Commits (4)
using pEp.DPE.Interfaces; using pEp.DPE.Interfaces;
using pEp.Extensions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace pEp.DPE namespace pEp.DPE
{ {
internal class DistributedPolicyEngine : IDistributedPolicyEngine internal class DistributedPolicyEngine : IDistributedPolicyEngine
{ {
private readonly static string DPE_FOLDER = Path.Combine(Globals.PEPUserFolder, "DPE"); private readonly static string DPE_FOLDER = Path.Combine(Globals.PEPUserFolder, "DPE");
private readonly static string PATCH_EXTENSION = ".patch"; public readonly static string DPE_BACKUP_LOCATION = Path.Combine(DistributedPolicyEngine.DPE_FOLDER, "temp");
private const string PATCH_EXTENSION = ".patch";
private const string PATCH_MESSAGE_SUBJECT = "Configuration changes";
public const string DPE_MESSAGE_CLASS = "IPM.Note.DPE";
private readonly PEPIdentity ownIdentity;
private readonly PatchEvents patchEvents; private readonly PatchEvents patchEvents;
/// <summary> /// <summary>
...@@ -19,16 +26,17 @@ namespace pEp.DPE ...@@ -19,16 +26,17 @@ namespace pEp.DPE
{ {
this.patchEvents = new PatchEvents(); this.patchEvents = new PatchEvents();
this.Subscribe(this.patchEvents); this.Subscribe(this.patchEvents);
this.ownIdentity = new PEPIdentity(Globals.ThisAddIn.Settings.AccountSettingsList.First().SmtpAddress);
} }
#region Event handlers #region Event handlers
/// <summary> /// <summary>
/// Event handler for when a patch has been accepted. /// Event handler for when a patch has been accepted.
/// </summary> /// </summary>
private void PatchEvents_PatchAccepted(object sender, PatchEventArgs e) private void PatchEvents_PatchAccepted(object sender, PatchEventArgs e)
{ {
ThisAddIn.PEPEngine.ShowNotification("pEp Distributed Policy Engine", $"Patch with id {e.Patch.Id} was accepted."); ThisAddIn.PEPEngine.ShowNotification("pEp Distributed Policy Engine", $"Patch with id { e.Patch.Id } was accepted.");
} }
/// <summary> /// <summary>
...@@ -36,7 +44,7 @@ namespace pEp.DPE ...@@ -36,7 +44,7 @@ namespace pEp.DPE
/// </summary> /// </summary>
private void PatchEvents_PatchRejected(object sender, PatchEventArgs e) private void PatchEvents_PatchRejected(object sender, PatchEventArgs e)
{ {
ThisAddIn.PEPEngine.ShowNotification("pEp Distributed Policy Engine", $"Patch with id {e.Patch.Id} was rejected."); ThisAddIn.PEPEngine.ShowNotification("pEp Distributed Policy Engine", $"Patch with id { e.Patch.Id } was rejected.");
} }
/// <summary> /// <summary>
...@@ -46,10 +54,58 @@ namespace pEp.DPE ...@@ -46,10 +54,58 @@ namespace pEp.DPE
{ {
// Save the patch and show a notification // Save the patch and show a notification
Patch patch = e.Patch; Patch patch = e.Patch;
this.SavePatch(patch); this.CreatePatchMailItem(patch, e.Submitter);
ThisAddIn.PEPEngine.ShowNotification("New patch suggested", patch.CommitMessage + " " + patch.Diff); ThisAddIn.PEPEngine.ShowNotification("New patch suggested", patch.CommitMessage + " " + patch.Diff);
} }
/// <summary>
/// Creates a mail item that shows a suggested patch.
/// </summary>
/// <param name="patch">The patch to show.</param>
/// <param name="submitter">The submitter of the patch.</param>
private void CreatePatchMailItem(Patch patch, PEPIdentity submitter)
{
Outlook.MailItem omi = null;
Outlook.MailItem mi = null;
try
{
// Use a PEPMessage to define the message to create
PEPMessage patchMessage = new PEPMessage
{
From = submitter,
LongMsgFormattedHtml = patch.Serialize(),
ShortMsg = DistributedPolicyEngine.PATCH_MESSAGE_SUBJECT,
};
patchMessage.To.Add(this.ownIdentity);
// Create the mail item and apply the PEPMessage
omi = Globals.ThisAddIn.Application.CreateItem(Outlook.OlItemType.olMailItem) as Outlook.MailItem;
patchMessage.ApplyTo(omi, false, true, true);
// Add custom message class, set time, and mark as Sent (so that it won't appear as draft)
MapiHelper.SetProperties(omi, new MAPIProperties
{
{ MapiProperty.PidTagMessageClass, DistributedPolicyEngine.DPE_MESSAGE_CLASS },
{ MapiProperty.PidTagMessageDeliveryTime, DateTime.UtcNow }
});
omi.SetMessageFlag(MapiPropertyValue.EnumPidTagMessageFlags.mfUnsent, false);
// Move to inbox and set Received time
mi = omi.Move(Globals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox));
mi.Save();
}
catch (Exception ex)
{
Log.Error("CreatePatchMailItem: Error creating patch mail item. " + ex);
}
finally
{
omi = null;
mi = null;
}
}
#endregion #endregion
#region Methods #region Methods
...@@ -115,9 +171,9 @@ namespace pEp.DPE ...@@ -115,9 +171,9 @@ namespace pEp.DPE
/// <param name="patchEvents">The patch events to unsubscribe from.</param> /// <param name="patchEvents">The patch events to unsubscribe from.</param>
public void Unsubscribe(PatchEvents patchEvents) public void Unsubscribe(PatchEvents patchEvents)
{ {
patchEvents.PatchAccepted += PatchEvents_PatchAccepted; patchEvents.PatchAccepted -= PatchEvents_PatchAccepted;
patchEvents.PatchRejected += PatchEvents_PatchRejected; patchEvents.PatchRejected -= PatchEvents_PatchRejected;
patchEvents.PatchSuggested += PatchEvents_PatchSuggested; patchEvents.PatchSuggested -= PatchEvents_PatchSuggested;
} }
/// <summary> /// <summary>
...@@ -146,27 +202,6 @@ namespace pEp.DPE ...@@ -146,27 +202,6 @@ namespace pEp.DPE
return false; return false;
} }
/// <summary>
/// Saves the patch to disk.
/// </summary>
/// <param name="patch">The patch to save.</param>
/// <returns>True if the patch was saved successfully, otherwise false.</returns>
private bool SavePatch(Patch patch)
{
try
{
string fileName = Path.Combine(DistributedPolicyEngine.DPE_FOLDER, patch.Id + DistributedPolicyEngine.PATCH_EXTENSION);
string xml = patch.Serialize();
File.WriteAllText(fileName, xml);
return File.Exists(fileName);
}
catch (Exception ex)
{
Log.Error("SavePatch: Error saving patch. " + ex.ToString());
}
return false;
}
#endregion #endregion
#region Static methods #region Static methods
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{ {
internal interface IPatchEvents internal interface IPatchEvents
{ {
void Suggested(Patch patch); void Suggested(Patch patch, PEPIdentity submitter);
void Rejected(Patch patch, Patch.RejectReason rejectReason); void Rejected(Patch patch, Patch.RejectReason rejectReason);
void Accepted(Patch patch); void Accepted(Patch patch);
} }
......
...@@ -4,7 +4,8 @@ namespace pEp.DPE ...@@ -4,7 +4,8 @@ namespace pEp.DPE
{ {
public class PatchEventArgs : EventArgs public class PatchEventArgs : EventArgs
{ {
public Patch Patch { get; set; } public Patch Patch { get; set; }
public Patch.RejectReason Reason { get; set; } = Patch.RejectReason.None; internal PEPIdentity Submitter { get; set; }
public Patch.RejectReason Reason { get; set; } = Patch.RejectReason.None;
} }
} }
...@@ -33,7 +33,7 @@ namespace pEp.DPE ...@@ -33,7 +33,7 @@ namespace pEp.DPE
Patch patch; Patch patch;
if ((patch = Patch.Deserialize(e.Request)) != null) if ((patch = Patch.Deserialize(e.Request)) != null)
{ {
this.Suggested(patch); this.Suggested(patch, new PEPIdentity("patchadmin@pep.security"));
} }
} }
...@@ -72,9 +72,9 @@ namespace pEp.DPE ...@@ -72,9 +72,9 @@ namespace pEp.DPE
/// Raises the Patch suggested event. /// Raises the Patch suggested event.
/// </summary> /// </summary>
/// <param name="patch">The patch that has been suggested.</param> /// <param name="patch">The patch that has been suggested.</param>
public void Suggested(Patch patch) public void Suggested(Patch patch, PEPIdentity submitter)
{ {
this.PatchSuggested?.Invoke(this, new PatchEventArgs { Patch = patch }); this.PatchSuggested?.Invoke(this, new PatchEventArgs { Patch = patch, Submitter = submitter });
} }
#endregion #endregion
......
...@@ -578,6 +578,8 @@ namespace pEp ...@@ -578,6 +578,8 @@ namespace pEp
[FieldOffset(0)] [FieldOffset(0)]
public double at; public double at;
[FieldOffset(0)] [FieldOffset(0)]
public System.Runtime.InteropServices.ComTypes.FILETIME ft;
[FieldOffset(0)]
public IntPtr lpszA; public IntPtr lpszA;
[FieldOffset(0)] [FieldOffset(0)]
public IntPtr lpszW; public IntPtr lpszW;
...@@ -1455,6 +1457,23 @@ namespace pEp ...@@ -1455,6 +1457,23 @@ namespace pEp
} }
} }
break; break;
case MapiProperty.MapiDataType.PtypTime:
{
try
{
long fileTime = ((DateTime)value).ToFileTimeUtc();
sPropValue.Value.ft = new System.Runtime.InteropServices.ComTypes.FILETIME
{
dwLowDateTime = (int)(fileTime & 0xFFFFFFFF),
dwHighDateTime = (int)(fileTime >> 32)
};
}
catch (Exception ex)
{
throw new Exception(string.Format("Error converting to Filetime. Property tag: {0}. Value: {1}. Exception: {2}.", property.DaslName, value?.ToString(), ex.ToString()));
}
}
break;
default: default:
{ {
throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), property.DataType))); throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), property.DataType)));
...@@ -1769,6 +1788,23 @@ namespace pEp ...@@ -1769,6 +1788,23 @@ namespace pEp
} }
} }
break; break;
case MapiProperty.MapiDataType.PtypTime:
{
try
{
long fileTime = ((DateTime)value).ToFileTimeUtc();
sPropValue.Value.ft = new System.Runtime.InteropServices.ComTypes.FILETIME
{
dwLowDateTime = (int)(fileTime & 0xFFFFFFFF),
dwHighDateTime = (int)(fileTime >> 32)
};
}
catch (Exception ex)
{
throw new Exception(string.Format("Error converting to Filetime. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
}
}
break;
default: default:
{ {
throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), mapiProperty.DataType))); throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), mapiProperty.DataType)));
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
using pEp.UI.Models; using pEp.UI.Models;
using pEp.UI.ViewModels; using pEp.UI.ViewModels;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Office = Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook; using Outlook = Microsoft.Office.Interop.Outlook;
namespace pEp namespace pEp
...@@ -14,7 +10,7 @@ namespace pEp ...@@ -14,7 +10,7 @@ namespace pEp
{ {
#region Form Region Factory #region Form Region Factory
[Microsoft.Office.Tools.Outlook.FormRegionMessageClass("IPM.Note.DPE")] [Microsoft.Office.Tools.Outlook.FormRegionMessageClass(DistributedPolicyEngine.DPE_MESSAGE_CLASS)]
[Microsoft.Office.Tools.Outlook.FormRegionName("pEpForOutlook.FormRegionDPE")] [Microsoft.Office.Tools.Outlook.FormRegionName("pEpForOutlook.FormRegionDPE")]
public partial class FormRegionDPEFactory public partial class FormRegionDPEFactory
{ {
...@@ -39,8 +35,8 @@ namespace pEp ...@@ -39,8 +35,8 @@ namespace pEp
omi = this.OutlookItem as Outlook.MailItem; omi = this.OutlookItem as Outlook.MailItem;
string xml = omi.HTMLBody; string xml = omi.HTMLBody;
Patch patch = Patch.Deserialize(xml); Patch patch = Patch.Deserialize(xml);
FormControlPatchViewModel formControlPatchViewModel = new FormControlPatchViewModel(patch, PatchDialog.PatchAction.SupportOrRejectPatch); PEPIdentity.GetFromIdentity(omi, out PEPIdentity submitter);
this.FormControlPatchView.DataContext = formControlPatchViewModel; this.FormControlPatchView.DataContext = new FormControlPatchViewModel(patch, submitter);
} }
catch (Exception ex) catch (Exception ex)
{ {
......
...@@ -62,11 +62,11 @@ namespace pEp.UI.Models ...@@ -62,11 +62,11 @@ namespace pEp.UI.Models
{ {
// Get group manager // Get group manager
pEpIdentity groupManager = (pEpIdentity)AdapterExtensions.ExecuteWithPassphraseCheck(() => ThisAddIn.PEPEngine.GroupQueryManager(groupIdentity)); pEpIdentity groupManager = (pEpIdentity)AdapterExtensions.ExecuteWithPassphraseCheck(() => ThisAddIn.PEPEngine.GroupQueryManager(groupIdentity));
Log.Verbose("GetManagedGroupe: Retrieved group manager " + groupManager.Address); Log.Verbose("GetManagedGroups: Retrieved group manager " + groupManager.Address);
// Get group members // Get group members
pEpIdentity[] members = (pEpIdentity[])AdapterExtensions.ExecuteWithPassphraseCheck(() => ThisAddIn.PEPEngine.GroupQueryMembers(groupIdentity)); pEpIdentity[] members = (pEpIdentity[])AdapterExtensions.ExecuteWithPassphraseCheck(() => ThisAddIn.PEPEngine.GroupQueryMembers(groupIdentity));
Log.Verbose("GetManagedGroupe: Retrieved group {0} group members", members.Length); Log.Verbose("GetManagedGroups: Retrieved group {0} group members", members.Length);
// Add group // Add group
messageGroups.Add(new MessageGroup(groupIdentity, groupManager, members)); messageGroups.Add(new MessageGroup(groupIdentity, groupManager, members));
......
using pEp.DPE; using pEp.DPE;
using pEp.UI.Models;
using System;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Media; using System.Windows.Media;
...@@ -12,13 +10,6 @@ namespace pEp.UI.ViewModels ...@@ -12,13 +10,6 @@ namespace pEp.UI.ViewModels
private Patch patch; private Patch patch;
private FlowDocument _DisplayDiff = null;
private bool _IsCommitMessageValid = false;
private bool _IsDiffValid = false;
private bool _IsUriValid = false;
private bool _IsValid = false;
private RelayCommand _LoadFromFileCommand = null;
#endregion #endregion
#region Properties #region Properties
...@@ -32,11 +23,15 @@ namespace pEp.UI.ViewModels ...@@ -32,11 +23,15 @@ namespace pEp.UI.ViewModels
set set
{ {
this.patch.CommitMessage = value; this.patch.CommitMessage = value;
this.ValidatePatch();
this.OnPropertyChanged(); this.OnPropertyChanged();
} }
} }
/// <summary>
/// Gets the creation date as string.
/// </summary>
public string CreationDateString => this.patch?.CreationDate.ToString("F");
/// <summary> /// <summary>
/// The diff of this patch. /// The diff of this patch.
/// </summary> /// </summary>
...@@ -46,7 +41,6 @@ namespace pEp.UI.ViewModels ...@@ -46,7 +41,6 @@ namespace pEp.UI.ViewModels
set set
{ {
this.patch.Diff = value; this.patch.Diff = value;
this.ValidatePatch();
this.OnPropertyChanged(); this.OnPropertyChanged();
} }
} }
...@@ -54,55 +48,13 @@ namespace pEp.UI.ViewModels ...@@ -54,55 +48,13 @@ namespace pEp.UI.ViewModels
/// <summary> /// <summary>
/// Gets the diff of this patch as formatted flow document. /// Gets the diff of this patch as formatted flow document.
/// </summary> /// </summary>
public FlowDocument DisplayDiff { get => this._DisplayDiff; set => SetProperty(ref this._DisplayDiff, value); } public FlowDocument DisplayDiff { get; }
/// <summary> /// <summary>
/// The explanation shown in the UI regarding this patch. /// The explanation shown in the UI regarding this patch.
/// </summary> /// </summary>
public string Explanation { get; } public string Explanation { get; }
/// <summary>
/// Gets whether the Cancel button is visible.
/// </summary>
public bool IsCancelButtonVisible { get; } = true;
/// <summary>
/// Gets or sets whether the commit message is valid.
/// </summary>
public bool IsCommitMessageValid
{
get => this._IsCommitMessageValid;
set
{
if (value != this._IsCommitMessageValid)
{
this._IsCommitMessageValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary>
/// Gets or sets whether the diff is valid.
/// </summary>
public bool IsDiffValid
{
get => this._IsDiffValid;
set
{
if (value != this._IsDiffValid)
{
this._IsDiffValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary>
/// Gets whether the patch in this dialog is editable.
/// </summary>
public bool IsEditable { get; } = false;
/// <summary> /// <summary>
/// Gets whether the OK button is visible. /// Gets whether the OK button is visible.
/// </summary> /// </summary>
...@@ -113,58 +65,10 @@ namespace pEp.UI.ViewModels ...@@ -113,58 +65,10 @@ namespace pEp.UI.ViewModels
/// </summary> /// </summary>
public bool IsRejectButtonVisible { get; } = false; public bool IsRejectButtonVisible { get; } = false;
/// <summary>
/// Gets or sets whether the URI is valid.
/// </summary>
public bool IsUriValid
{
get => this._IsUriValid;
set
{
if (value != this._IsUriValid)
{
this._IsUriValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary>
/// Gets or sets whether this patch is valid.
/// </summary>
public bool IsValid
{
get => this._IsValid;
set
{
if (value != this._IsValid)
{
this._IsValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary>
/// Gets the command to load the diff from file.
/// </summary>
public RelayCommand LoadFromFileCommand
{
get
{
if (this._LoadFromFileCommand == null)
{
this._LoadFromFileCommand = new RelayCommand(LoadFromFile);
}
return this._LoadFromFileCommand;
}
}
/// <summary> /// <summary>
/// The command to accept the patch dialog. /// The command to accept the patch dialog.
/// </summary> /// </summary>
public RelayCommand OKButtonCommand => new RelayCommand(this.SupportPatch, p => this.IsValid); public RelayCommand OKButtonCommand => new RelayCommand(this.SupportPatch);
/// <summary> /// <summary>
/// Gets the OK button text. /// Gets the OK button text.
...@@ -181,6 +85,11 @@ namespace pEp.UI.ViewModels ...@@ -181,6 +85,11 @@ namespace pEp.UI.ViewModels
/// </summary> /// </summary>
public string RejectButtonText { get; } = Properties.Resources.SyncWizard_RejectButton; public string RejectButtonText { get; } = Properties.Resources.SyncWizard_RejectButton;
/// <summary>
/// Gets the submitter of this patch.
/// </summary>
public PEPIdentity Submitter { get; }
/// <summary> /// <summary>
/// Gets or sets the tag of the patch. /// Gets or sets the tag of the patch.
/// </summary> /// </summary>
...@@ -203,7 +112,6 @@ namespace pEp.UI.ViewModels ...@@ -203,7 +112,6 @@ namespace pEp.UI.ViewModels
set set
{ {
this.patch.Uri = value; this.patch.Uri = value;
this.ValidatePatch();
this.OnPropertyChanged(); this.OnPropertyChanged();
} }
} }
...@@ -212,39 +120,19 @@ namespace pEp.UI.ViewModels ...@@ -212,39 +120,19 @@ namespace pEp.UI.ViewModels
#region Constructors #region Constructors
public FormControlPatchViewModel(Patch patch, PatchDialog.PatchAction patchAction, bool isEditable = false) /// <summary>
/// Primary constructor.
/// </summary>
/// <param name="patch">The patch to create the form region with.</param>
/// <param name="submitter">The submitter of the patch.</param>
public FormControlPatchViewModel(Patch patch, PEPIdentity submitter)
{ {
this.patch = patch; this.patch = patch;
this.IsEditable = isEditable; this.Submitter = submitter;
this.Explanation = "New configuration changes pending approval";
switch (patchAction) this.IsRejectButtonVisible = true;
{ this.OKButtonText = "Support";
case PatchDialog.PatchAction.EditPatch:
break;
case PatchDialog.PatchAction.NewPatch:
{
this.Explanation = "New patch";
}
break;
case PatchDialog.PatchAction.ShowPatch:
{
this.Explanation = "Patch " + patch.Id;
this.IsCancelButtonVisible = false;
}
break;
case PatchDialog.PatchAction.SupportOrRejectPatch:
{
this.Explanation = "Support or reject patch";
this.IsRejectButtonVisible = true;
this.OKButtonText = "Support";
}
break;
default:
break;
}
this.DisplayDiff = this.FormatDiff(); this.DisplayDiff = this.FormatDiff();
this.ValidatePatch();
} }
#endregion #endregion
...@@ -266,7 +154,7 @@ namespace pEp.UI.ViewModels ...@@ -266,7 +154,7 @@ namespace pEp.UI.ViewModels
{ {
FontFamily = new FontFamily("Courier New"), FontFamily = new FontFamily("Courier New"),
FontSize = 12.0, FontSize = 12.0,
Background = Brushes.White, Background = Brushes.White
}; };
string[] lines = this.Diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { }; string[] lines = this.Diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { };
...@@ -297,31 +185,22 @@ namespace pEp.UI.ViewModels ...@@ -297,31 +185,22 @@ namespace pEp.UI.ViewModels
return document; return document;
} }
private void LoadFromFile(object parameter) /// <summary>
{ /// Rejects this patch.
// Get diff from disk /// </summary>
} /// <param name="parameter">The command parameter.</param>
private void RejectPatch(object parameter) private void RejectPatch(object parameter)
{ {
Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity()); Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity());
} }
private void SupportPatch(object parameter)
{
Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity());
}
/// <summary> /// <summary>
/// Validates the patch. /// Supports this patch.
/// </summary> /// </summary>
private void ValidatePatch() /// <param name="parameter">The command parameter.</param>
private void SupportPatch(object parameter)
{ {
this.IsCommitMessageValid = !string.IsNullOrEmpty(this.CommitMessage); Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity());
this.IsDiffValid = !string.IsNullOrEmpty(this.Diff);
this.IsUriValid = !string.IsNullOrEmpty(this.Uri);
this.IsValid = this.IsCommitMessageValid && this.IsDiffValid && this.IsUriValid;
} }
#endregion #endregion
......
using Microsoft.Win32; using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model;
using Microsoft.Win32;
using pEp.DPE;
using pEp.UI.Models; using pEp.UI.Models;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Media; using System.Windows.Media;
...@@ -11,12 +18,12 @@ namespace pEp.UI.ViewModels ...@@ -11,12 +18,12 @@ namespace pEp.UI.ViewModels
{ {
#region Fields #region Fields
private FlowDocument _DisplayDiff = null; private bool _IsCommitMessageValid = false;
private bool _IsCommitMessageValid = false; private bool _IsDiffValid = false;
private bool _IsDiffValid = false; private bool _IsValid = false;
private bool _IsUriValid = false; private RelayCommand _LoadFromFileCommand = null;
private bool _IsValid = false; private Tuple<string, string, string> _SelectedFile = null;
private RelayCommand _LoadFromFileCommand = null; private FlowDocument _VisibleDiff = null;
#endregion #endregion
...@@ -51,25 +58,6 @@ namespace pEp.UI.ViewModels ...@@ -51,25 +58,6 @@ namespace pEp.UI.ViewModels
/// </summary> /// </summary>
public new PatchDialog Dialog => base.Dialog as PatchDialog; public new PatchDialog Dialog => base.Dialog as PatchDialog;
/// <summary>
/// The diff of this patch.
/// </summary>
public string Diff
{
get => this.Dialog.Patch?.Diff;
set
{
this.Dialog.Patch.Diff = value;
this.ValidatePatch();
this.OnPropertyChanged();
}
}
/// <summary>
/// Gets the diff of this patch as formatted flow document.
/// </summary>
public FlowDocument DisplayDiff { get => this._DisplayDiff; set => SetProperty(ref this._DisplayDiff, value); }
/// <summary> /// <summary>
/// The explanation shown in the UI regarding this patch. /// The explanation shown in the UI regarding this patch.
/// </summary> /// </summary>
...@@ -127,22 +115,6 @@ namespace pEp.UI.ViewModels ...@@ -127,22 +115,6 @@ namespace pEp.UI.ViewModels
/// </summary> /// </summary>
public bool IsRejectButtonVisible { get; } = false; public bool IsRejectButtonVisible { get; } = false;
/// <summary>
/// Gets or sets whether the URI is valid.
/// </summary>
public bool IsUriValid
{
get => this._IsUriValid;
set
{
if (value != this._IsUriValid)
{
this._IsUriValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary> /// <summary>
/// Gets or sets whether this patch is valid. /// Gets or sets whether this patch is valid.
/// </summary> /// </summary>
...@@ -168,17 +140,22 @@ namespace pEp.UI.ViewModels ...@@ -168,17 +140,22 @@ namespace pEp.UI.ViewModels
{ {
if (this._LoadFromFileCommand == null) if (this._LoadFromFileCommand == null)
{ {
this._LoadFromFileCommand = new RelayCommand(LoadFromFile); this._LoadFromFileCommand = new RelayCommand(this.AddFile);
} }
return this._LoadFromFileCommand; return this._LoadFromFileCommand;
} }
} }
/// <summary>
/// Gets the collection of config files that are being modified.
/// </summary>
public ObservableCollection<Tuple<string, string, string>> ConfigFiles { get; } = new ObservableCollection<Tuple<string, string, string>>();
/// <summary> /// <summary>
/// The command to accept the patch dialog. /// The command to accept the patch dialog.
/// </summary> /// </summary>
public RelayCommand OKButtonCommand => new RelayCommand(p => this.Close(true), p => this.IsValid); public RelayCommand OKButtonCommand => new RelayCommand(this.CreateAndSuggestPatch, p => this.IsValid);
/// <summary> /// <summary>
/// Gets the OK button text. /// Gets the OK button text.
...@@ -186,14 +163,22 @@ namespace pEp.UI.ViewModels ...@@ -186,14 +163,22 @@ namespace pEp.UI.ViewModels
public string OKButtonText { get; } = Properties.Resources.Options_OKText; public string OKButtonText { get; } = Properties.Resources.Options_OKText;
/// <summary> /// <summary>
/// The command to reject the dialog. /// Gets the Remove files button command
/// </summary> /// </summary>
public RelayCommand RejectButtonCommand => new RelayCommand(p => this.Close(false)); public RelayCommand RemoveButtonCommand => new RelayCommand(this.RemoveFile);
/// <summary> /// <summary>
/// Gets the Reject button text. /// Gets the currently selected file.
/// </summary> /// </summary>
public string RejectButtonText { get; } = Properties.Resources.SyncWizard_RejectButton; public Tuple<string, string, string> SelectedFile
{
get => this._SelectedFile;
set
{
this._SelectedFile = value;
this.UpdateVisibleDiff();
}
}
/// <summary> /// <summary>
/// Gets or sets the tag of the patch. /// Gets or sets the tag of the patch.
...@@ -209,18 +194,9 @@ namespace pEp.UI.ViewModels ...@@ -209,18 +194,9 @@ namespace pEp.UI.ViewModels
} }
/// <summary> /// <summary>
/// Gets or sets the URI of the patch. /// Gets the diff of the selected file as formatted flow document.
/// </summary> /// </summary>
public string Uri public FlowDocument VisibleDiff { get => this._VisibleDiff; set => this.SetProperty(ref this._VisibleDiff, value); }
{
get => this.Dialog.Patch?.Uri;
set
{
this.Dialog.Patch.Uri = value;
this.ValidatePatch();
this.OnPropertyChanged();
}
}
#endregion #endregion
...@@ -235,19 +211,11 @@ namespace pEp.UI.ViewModels ...@@ -235,19 +211,11 @@ namespace pEp.UI.ViewModels
{ {
switch (dialog.PatchActionType) switch (dialog.PatchActionType)
{ {
case PatchDialog.PatchAction.EditPatch:
break;
case PatchDialog.PatchAction.NewPatch: case PatchDialog.PatchAction.NewPatch:
{ {
this.Explanation = "New patch"; this.Explanation = "New patch";
} }
break; break;
case PatchDialog.PatchAction.ShowPatch:
{
this.Explanation = "Patch " + dialog.Patch.Id;
this.IsCancelButtonVisible = false;
}
break;
case PatchDialog.PatchAction.SupportOrRejectPatch: case PatchDialog.PatchAction.SupportOrRejectPatch:
{ {
this.Explanation = "Support or reject patch"; this.Explanation = "Support or reject patch";
...@@ -255,11 +223,12 @@ namespace pEp.UI.ViewModels ...@@ -255,11 +223,12 @@ namespace pEp.UI.ViewModels
this.OKButtonText = "Support"; this.OKButtonText = "Support";
} }
break; break;
case PatchDialog.PatchAction.EditPatch:
case PatchDialog.PatchAction.ShowPatch:
default: default:
break; break;
} }
this.DisplayDiff = this.FormatDiff();
this.ValidatePatch(); this.ValidatePatch();
} }
...@@ -267,13 +236,324 @@ namespace pEp.UI.ViewModels ...@@ -267,13 +236,324 @@ namespace pEp.UI.ViewModels
#region Methods #region Methods
/// <summary>
/// Adds a new file to the list of modified config files.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private void AddFile(object parameter)
{
// Create the dialog to select the key file
OpenFileDialog openFileDialog = new OpenFileDialog
{
CheckFileExists = true,
CheckPathExists = true,
Filter = "All files|*.*",
Multiselect = false
};
// Import the key file and set the key as default
if (openFileDialog.ShowDialog() == true)
{
this.EditFileAndCreateDiff(openFileDialog.FileName);
}
}
/// <summary>
/// Creates a patch from the view model and suggests it.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private void CreateAndSuggestPatch(object parameter)
{
// The patch was created right now
this.Dialog.Patch.CreationDate = DateTime.UtcNow;
// Concatenate all diffs from the selected files
this.Dialog.Patch.Diff = this.UnifyDiffs();
// Get a common root URI of all diff files
this.Dialog.Patch.Uri = this.GetRootUri();
// Close with result True.
this.Close(true);
}
/// <summary>
/// Creates a diff from two files.
/// </summary>
/// <param name="fileNameA">The old file.</param>
/// <param name="fileNameB">The new file.</param>
/// <returns>The diff between the two files.</returns>
private string CreateDiff(string fileNameA, string fileNameB)
{
if (!(File.Exists(fileNameA) && File.Exists(fileNameB)))
{
Log.ErrorAndFailInDebugMode("CreateDiff: Input file doesn't exist.");
return null;
}
string fileA, fileB;
try
{
fileA = File.ReadAllText(fileNameA);
fileB = File.ReadAllText(fileNameB);
}
catch (Exception ex)
{
Log.Error("CreateDiff: Error reading input file. " + ex);
return null;
}
/* Create a diff string with sections of the following format:
*
* --- a/path/to/fileA
* +++ b/path/to/fileB
* @@ -1,7 +1,7 @@
* string a = "A";
* string b = "B";
* string c = "C"
* -string d = "D";
* +string e = "E";
*
* if (string.IsNullOrEmpty(a))
* {
*
* => Line number and line count of affected lines in file A.
* => Line number and line count of affected lines in file B.
* => Three unchanged lines (if available) before the modified part.
* => The modified part.
* => Three unchanged lines (if available) after the modified part.
*/
string diff = $"---a/{ fileNameB }\n+++b/{ fileNameB }\n";
SideBySideDiffModel sideBySideDiffModel = SideBySideDiffBuilder.Diff(fileA, fileB);
List<DiffPiece> newLines = sideBySideDiffModel.NewText.Lines;
List<DiffPiece> oldLines = sideBySideDiffModel.OldText.Lines;
List<string> block = new List<string>();
int unchangedCount = 0;
bool newBlock = true;
int oldFileIndex = 0, newFileIndex = 0;
for (int i = 0; i < newLines.Count; i++)
{
// Add lines as necessary
if ((newLines[i].Type == ChangeType.Unchanged) &&
(block.Count > 0))
{
block.Add(" " + newLines[i].Text);
unchangedCount++;
}
else if (newLines[i].Type == ChangeType.Imaginary)
{
block.Add("-" + oldLines[i].Text);
unchangedCount = 0;
}
else if (newLines[i].Type == ChangeType.Inserted)
{
block.Add("-" + oldLines[i].Text);
unchangedCount = 0;
}
else if (newLines[i].Type == ChangeType.Modified)
{
block.Add("-" + oldLines[i].Text);
block.Add("+" + newLines[i].Text);
unchangedCount = 0;
}
// If a block is being constructed, add additional parts as necessary
if (block.Count > 0)
{
// If we have a new block, add the three preceding unchanged lines
if (newBlock)
{
newBlock = false;
int index = i;
for (int k = 0; k < 3; k++)
{
if (index-- > 0)
{
block.Insert(0, " " + newLines[index].Text);
}
}
index++;
newFileIndex = newLines[index].Position ?? 1;
oldFileIndex = oldLines[index].Position ?? 1;
}
else if ((unchangedCount > 2) || (i == newLines.Count - 1))
{
// Once a block is complete, add descriptor at the beginning and add lines to diff
int oldFileLineCount = block.Count(s => !s.StartsWith("+"));
int newFileLineCount = block.Count(s => !s.StartsWith("-"));
diff += $"@@ -{ oldFileIndex },{ oldFileLineCount } +{ newFileIndex },{ newFileLineCount } @@\n";
foreach (var line in block)
{
diff += line + '\n';
}
block = new List<string>();
newBlock = true;
}
}
}
return diff.TrimEnd('\n');
}
//private string CreateDiff(string fileNameA, string fileNameB)
//{
// if (!(File.Exists(fileNameA) && File.Exists(fileNameB)))
// {
// Log.ErrorAndFailInDebugMode("CreateDiff: Input file doesn't exist.");
// return null;
// }
// string fileA, fileB;
// try
// {
// fileA = File.ReadAllText(fileNameA);
// fileB = File.ReadAllText(fileNameB);
// }
// catch (Exception ex)
// {
// Log.Error("CreateDiff: Error reading input file. " + ex);
// return null;
// }
// string diff = $"---a/{ fileNameB }\n+++b/{ fileNameB }\n";
// DiffPaneModel diffPaneModel = InlineDiffBuilder.Diff(fileA, fileB);
// List<DiffPiece> lines = diffPaneModel.Lines;
// List<DiffPiece> block = new List<DiffPiece>();
// for (int i = 0; i < lines.Count; i++)
// {
// if (lines[i].Type == ChangeType.Deleted || lines[i].Type == ChangeType.Inserted)
// {
// if (block.Count == 0)
// {
// int index = i - 3;
// do
// {
// if (index < 0)
// {
// continue;
// }
// if (lines[index].Type == ChangeType.Unchanged)
// {
// block.Add(lines[index]);
// }
// } while (++index < i);
// }
// block.Add(lines[i]);
// }
// else if (block.Count > 0)
// {
// block.Add(lines[i]);
// int index = 0;
// while ((++i < lines.Count) &&
// (++index < 3))
// {
// if (lines[i].Type == ChangeType.Unchanged)
// {
// block.Add(lines[i]);
// }
// else
// {
// break;
// }
// }
// if ((index == 3) || (i == lines.Count))
// {
// diff += $"@@ -{ block.First().Position },{ block.Last().Position } +{1},{1} @@\n";
// foreach (var diffPiece in block)
// {
// switch (diffPiece.Type)
// {
// case ChangeType.Unchanged:
// diff += $" { diffPiece.Text }\n";
// break;
// case ChangeType.Deleted:
// diff += $"-{ diffPiece.Text }\n";
// break;
// case ChangeType.Inserted:
// diff += $"+{ diffPiece.Text }\n";
// break;
// case ChangeType.Imaginary:
// case ChangeType.Modified:
// default:
// Log.ErrorAndFailInDebugMode("CreateDiff: Untreated diff piece.");
// break;
// }
// }
// block = new List<DiffPiece>();
// }
// }
// }
// return diff.TrimEnd('\n');
//}
/// <summary>
/// Opens the given file to edit and creates a diff once the editor is closed.
/// </summary>
/// <param name="fileName">The file name to edit and create a diff for.</param>
private void EditFileAndCreateDiff(string fileName)
{
try
{
// Get the temp file that will actually be edited
string tempFileName;
if ((this.ConfigFiles.First(cf => cf.Item1.Equals(fileName)) is Tuple<string, string, string> configFile) &&
(string.IsNullOrEmpty(configFile.Item2) == false))
{
tempFileName = configFile.Item2;
}
else
{
tempFileName = Path.Combine(DistributedPolicyEngine.DPE_BACKUP_LOCATION, Path.GetFileName(fileName) + ".orig");
Directory.CreateDirectory(DistributedPolicyEngine.DPE_BACKUP_LOCATION);
File.Copy(fileName, tempFileName);
}
// Open temp file in VS Code for the user to edit
Process openCodeProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "code",
Arguments = tempFileName,
UseShellExecute = true,
CreateNoWindow = true
}
};
openCodeProcess.Start();
openCodeProcess.WaitForExit();
if (this.ConfigFiles.Remove(this.ConfigFiles.Where(f => (f.Item1?.Equals(fileName) == true) && f.Item2?.Equals(tempFileName) == true).First()))
{
string diff = this.CreateDiff(fileName, tempFileName);
this.ConfigFiles.Add(new Tuple<string, string, string>(fileName, tempFileName, diff));
}
}
catch (Exception ex)
{
Log.Error($"EditFileAndCreateDiff: Error reading file content from { fileName }: { ex }");
}
}
/// <summary> /// <summary>
/// Formats the diff displaying colors of changes. /// Formats the diff displaying colors of changes.
/// </summary> /// </summary>
/// <returns>The formatted diff or null if no diff is available.</returns> /// <returns>The formatted diff or null if no diff is available.</returns>
private FlowDocument FormatDiff() private FlowDocument FormatDiff(string diff)
{ {
if (string.IsNullOrEmpty(this.Diff)) if (string.IsNullOrEmpty(diff))
{ {
return null; return null;
} }
...@@ -285,7 +565,7 @@ namespace pEp.UI.ViewModels ...@@ -285,7 +565,7 @@ namespace pEp.UI.ViewModels
Background = Brushes.White, Background = Brushes.White,
}; };
string[] lines = this.Diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { }; string[] lines = diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { };
foreach (string line in lines) foreach (string line in lines)
{ {
Paragraph paragraph = new Paragraph(new Run(line)) Paragraph paragraph = new Paragraph(new Run(line))
...@@ -313,30 +593,63 @@ namespace pEp.UI.ViewModels ...@@ -313,30 +593,63 @@ namespace pEp.UI.ViewModels
return document; return document;
} }
private void LoadFromFile(object parameter) /// <summary>
/// Gets a common root URI from all selected files in the dialog.
/// </summary>
/// <returns>The root URI</returns>
private string GetRootUri()
{ {
// Create the dialog to select the key file string rootUri = this.ConfigFiles.First()?.Item1;
OpenFileDialog openFileDialog = new OpenFileDialog
{
CheckFileExists = true,
CheckPathExists = true,
Filter = "Diff files|*.diff;|Patch files|*.patch;|All files|*.*",
Multiselect = false
};
// Import the key file and set the key as default foreach (var modifiedFile in this.ConfigFiles)
if (openFileDialog.ShowDialog() == true)
{ {
try for (int i = 0; i < modifiedFile.Item1.Length; i++)
{
string fileContent = File.ReadAllText(openFileDialog.FileName);
this.Diff = fileContent;
}
catch (Exception ex)
{ {
Log.Error($"LoadFromFile: Error reading file content from { openFileDialog.FileName }: { ex }"); if ((rootUri.Length <= i) ||
(modifiedFile.Item1[i] == rootUri[i]))
{
continue;
}
rootUri = rootUri.Substring(0, i);
break;
} }
} }
return rootUri;
}
/// <summary>
/// Removes a file from the list of changed files
/// </summary>
/// <param name="parameter">The command parameter.</param>
private void RemoveFile(object parameter)
{
this.ConfigFiles.Remove(this.SelectedFile);
}
/// <summary>
/// Concatenates all diffs from the selected files into one string.
/// </summary>
/// <returns>The unified diff.</returns>
private string UnifyDiffs()
{
string diff = null;
foreach (var modifiedFile in this.ConfigFiles)
{
diff += modifiedFile.Item3 + "\n";
}
return diff.TrimEnd('\n');
}
/// <summary>
/// Updates the diff that is visible in the view pane.
/// </summary>
private void UpdateVisibleDiff()
{
this.VisibleDiff = this.FormatDiff(this.SelectedFile?.Item3);
} }
/// <summary> /// <summary>
...@@ -345,10 +658,9 @@ namespace pEp.UI.ViewModels ...@@ -345,10 +658,9 @@ namespace pEp.UI.ViewModels
private void ValidatePatch() private void ValidatePatch()
{ {
this.IsCommitMessageValid = !string.IsNullOrEmpty(this.CommitMessage); this.IsCommitMessageValid = !string.IsNullOrEmpty(this.CommitMessage);
this.IsDiffValid = !string.IsNullOrEmpty(this.Diff); this.IsDiffValid = this.ConfigFiles?.Count > 0;
this.IsUriValid = !string.IsNullOrEmpty(this.Uri);
this.IsValid = this.IsCommitMessageValid && this.IsDiffValid && this.IsUriValid; this.IsValid = this.IsCommitMessageValid && this.IsDiffValid;
} }
#endregion #endregion
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
xmlns:ui="clr-namespace:pEp.UI" xmlns:ui="clr-namespace:pEp.UI"
xmlns:vm="clr-namespace:pEp.UI.ViewModels" xmlns:vm="clr-namespace:pEp.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:PatchDialogViewModel}" FontFamily="Segoe UI"
FontSize="12"
d:DataContext="{d:DesignInstance Type=vm:FormControlPatchViewModel}"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
...@@ -24,10 +26,13 @@ ...@@ -24,10 +26,13 @@
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
...@@ -37,78 +42,82 @@ ...@@ -37,78 +42,82 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="0" Grid.Row="0"
Grid.ColumnSpan="2"
Content="{Binding Explanation}" Content="{Binding Explanation}"
Margin="10"/> FontWeight="Bold"
<Button Grid.Column="1" Margin="10,5"/>
Grid.Row="0"
Content="Load from file"
Margin="10"
HorizontalAlignment="Right"
Style="{StaticResource StyleButtonGray}"
Command="{Binding LoadFromFileCommand}"
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Content="Diff*" Grid.ColumnSpan="2"
Content="{Binding Submitter.DisplayString}"
Margin="10,1" />
<Label Grid.Column="0"
Grid.Row="2"
Grid.ColumnSpan="2"
Content="{Binding CreationDateString}"
Margin="10,1" />
<Image Grid.Column="2"
Grid.Row="0"
Grid.RowSpan="3"
Width="100"
Height="41"
Source="pack://application:,,,/pEp;component/Resources/ImageLogoMedium.png"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="5,5,10,5"/>
<Label Grid.Column="0"
Grid.Row="3"
Content="Diff"
Margin="10,10,5,10"/> Margin="10,10,5,10"/>
<ui:TextBoxWithPlaceholder Grid.Column="1"
Grid.Row="1"
Text="{Binding Diff, UpdateSourceTrigger=PropertyChanged}"
MultiLine="True"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}"
IsValidInput="{Binding IsDiffValid}"
Placeholder="Please add diff"
Height="400"
Width="600"
Margin="5,10,10,10"
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<FlowDocumentScrollViewer Grid.Column="1" <FlowDocumentScrollViewer Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="3"
BorderBrush="Black" BorderBrush="Black"
BorderThickness="1" BorderThickness="1"
Grid.Row="1"
MinHeight="400"
MinWidth="600"
Margin="5,10,10,10" Margin="5,10,10,10"
Document="{Binding DisplayDiff}" Document="{Binding DisplayDiff}" />
Visibility="{Binding IsEditable, Converter={StaticResource InvertBoolToVisibility}}"/>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="2" Grid.Row="4"
Content="Uri*" Content="Uri"
Margin="10,10,5,10"/> Margin="10,10,5,10"/>
<ui:TextBoxWithPlaceholder Grid.Column="1" <TextBox Grid.Column="1"
Grid.Row="2" Grid.ColumnSpan="2"
Text="{Binding Uri}" Grid.Row="4"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}" VerticalAlignment="Center"
IsValidInput="{Binding IsUriValid}" Padding="2"
Placeholder="URI" Text="{Binding Uri}"
MinWidth="400" IsReadOnly="True"
Margin="5,10,10,10"/> MinWidth="400"
Margin="5,10,10,10"/>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="3" Grid.Row="5"
Content="Commit message*" Content="Commit message"
Margin="10,10,5,10"/> Margin="10,10,5,10"/>
<ui:TextBoxWithPlaceholder Grid.Column="1" <TextBox Grid.Column="1"
Grid.Row="3" Grid.ColumnSpan="2"
Text="{Binding CommitMessage}" Grid.Row="5"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}" VerticalAlignment="Center"
IsValidInput="{Binding IsCommitMessageValid}" Padding="2"
Placeholder="Commit message" Text="{Binding CommitMessage}"
MinWidth="400" IsReadOnly="True"
Margin="5,10,10,10"/> MinWidth="400"
Margin="5,10,10,10"/>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="4" Grid.Row="6"
Content="Tag" Content="Tag"
Margin="10,10,5,10"/> Margin="10,10,5,10"/>
<ui:TextBoxWithPlaceholder Grid.Column="1" <TextBox Grid.Column="1"
Grid.Row="4" Grid.ColumnSpan="2"
Text="{Binding Tag}" Grid.Row="6"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}" VerticalAlignment="Center"
Placeholder="Tag (optional)" Padding="2"
MinWidth="400" Text="{Binding Tag}"
Margin="5,10,10,10"/> IsReadOnly="True"
MinWidth="400"
Margin="5,10,10,10"/>
<StackPanel Grid.Column="0" <StackPanel Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="3"
Grid.Row="5" Grid.Row="7"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Button Content="{Binding OKButtonText}" <Button Content="{Binding OKButtonText}"
......
<UserControl xmlns:UI="clr-namespace:pEp.UI" <UserControl x:Class="pEp.UI.Views.PatchDialogView"
x:Class="pEp.UI.Views.PatchDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p="clr-namespace:pEp.Properties"
xmlns:ui="clr-namespace:pEp.UI" xmlns:ui="clr-namespace:pEp.UI"
xmlns:vm="clr-namespace:pEp.UI.ViewModels" xmlns:vm="clr-namespace:pEp.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:PatchDialogViewModel}" d:DataContext="{d:DesignInstance Type=vm:PatchDialogViewModel}"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
...@@ -26,105 +24,83 @@ ...@@ -26,105 +24,83 @@
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Column="0" <Label Grid.Row="0"
Grid.Row="0" Grid.Column="0"
Content="{Binding Explanation}" Grid.ColumnSpan="3"
Content="Change config"
Margin="10"/> Margin="10"/>
<Button Grid.Column="1" <ListBox Grid.Row="1"
Grid.Row="0" Grid.Column="0"
Content="Load from file" Margin="10"
Margin="10" Width="500"
HorizontalAlignment="Right" Height="500"
Style="{StaticResource StyleButtonGray}" SelectedItem="{Binding SelectedFile}"
Command="{Binding LoadFromFileCommand}" /> ItemsSource="{Binding ConfigFiles}">
<Label Grid.Column="0" <ListBox.ItemTemplate>
Grid.Row="1" <DataTemplate>
Content="Diff*" <Label Content="{Binding Item1}" />
Margin="10,10,5,10"/> </DataTemplate>
<UI:TextBoxWithPlaceholder Grid.Column="1" </ListBox.ItemTemplate>
Grid.Row="1" </ListBox>
Text="{Binding Diff, UpdateSourceTrigger=PropertyChanged}" <StackPanel Grid.Row="1"
MultiLine="True" Grid.Column="1">
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}" <Button Content="Add file"
IsValidInput="{Binding IsDiffValid}" Margin="5"
Placeholder="Please add diff" Style="{StaticResource StyleButtonGray}"
Height="400" Command="{Binding LoadFromFileCommand}"/>
Width="600" <Button Content="Remove file"
Margin="5,10,10,10" Margin="5"
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/> Style="{StaticResource StyleButtonGray}"
<FlowDocumentScrollViewer Grid.Column="1" Command="{Binding RemoveButtonCommand}" />
</StackPanel>
<FlowDocumentScrollViewer Grid.Column="2"
Grid.Row="1" Grid.Row="1"
Height="400" Width="500"
Width="600" Height="500"
BorderBrush="Black"
BorderThickness="1"
Margin="5,10,10,10" Margin="5,10,10,10"
Document="{Binding DisplayDiff}" Document="{Binding VisibleDiff}"/>
Visibility="{Binding IsEditable, Converter={StaticResource InvertBoolToVisibility}}"/> <ui:TextBoxWithPlaceholder Grid.Column="0"
<Label Grid.Column="0" Grid.ColumnSpan="3"
Grid.Row="2"
Content="Uri*"
Margin="10,10,5,10"/>
<UI:TextBoxWithPlaceholder Grid.Column="1"
Grid.Row="2" Grid.Row="2"
Text="{Binding Uri}" Text="{Binding CommitMessage, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}"
IsValidInput="{Binding IsUriValid}"
Placeholder="URI"
MinWidth="400"
Margin="5,10,10,10"/>
<Label Grid.Column="0"
Grid.Row="3"
Content="Commit message*"
Margin="10,10,5,10"/>
<UI:TextBoxWithPlaceholder Grid.Column="1"
Grid.Row="3"
Text="{Binding CommitMessage}"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}"
IsValidInput="{Binding IsCommitMessageValid}" IsValidInput="{Binding IsCommitMessageValid}"
Placeholder="Commit message" Placeholder="Commit message"
MinWidth="400" Margin="5,10,10,10"
Margin="5,10,10,10"/> Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<Label Grid.Column="0" <ui:TextBoxWithPlaceholder Grid.Column="0"
Grid.Row="4" Grid.ColumnSpan="3"
Content="Tag" Grid.Row="3"
Margin="10,10,5,10"/> Text="{Binding Tag, UpdateSourceTrigger=PropertyChanged}"
<UI:TextBoxWithPlaceholder Grid.Column="1"
Grid.Row="4"
Text="{Binding Tag}"
IsReadOnly="{Binding IsEditable, Converter={StaticResource InvertBool}}"
Placeholder="Tag (optional)" Placeholder="Tag (optional)"
MinWidth="400" Margin="5,10,10,10"
Margin="5,10,10,10"/> Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<StackPanel Grid.Column="0" <StackPanel Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="3"
Grid.Row="5" Grid.Row="4"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Center"> HorizontalAlignment="Center"
Margin="10">
<Button Content="{Binding OKButtonText}" <Button Content="{Binding OKButtonText}"
Command="{Binding OKButtonCommand}" Command="{Binding OKButtonCommand}"
IsDefault="True" IsEnabled="{Binding IsValid}"
Visibility="{Binding IsOKButtonVisible, Converter={StaticResource BoolToVisibility}}" Margin="10"
Style="{StaticResource StyleButtonGray}" Style="{StaticResource StyleButtonGray}"/>
Margin="10" />
<Button Content="{Binding RejectButtonText}"
Command="{Binding RejectButtonCommand}"
Visibility="{Binding IsRejectButtonVisible, Converter={StaticResource BoolToVisibility}}"
Style="{StaticResource StyleButtonGray}"
Margin="10" />
<Button Content="{Binding CancelButtonText}" <Button Content="{Binding CancelButtonText}"
Margin="10"
Command="{Binding CancelButtonCommand}" Command="{Binding CancelButtonCommand}"
Visibility="{Binding IsCancelButtonVisible, Converter={StaticResource BoolToVisibility}}" Style="{StaticResource StyleButtonGray}"/>
Style="{StaticResource StyleButtonGray}"
Margin="10"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>
...@@ -251,6 +251,9 @@ ...@@ -251,6 +251,9 @@
--> -->
<ItemGroup> <ItemGroup>
<Reference Include="Accessibility" /> <Reference Include="Accessibility" />
<Reference Include="DiffPlex, Version=1.7.0.0, Culture=neutral, PublicKeyToken=1d35e91d1bd7bc0f, processorArchitecture=MSIL">
<HintPath>..\packages\DiffPlex.1.7.0\lib\net40\DiffPlex.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Office.Interop.Outlook, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"> <Reference Include="Microsoft.Office.Interop.Outlook, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
<EmbedInteropTypes>True</EmbedInteropTypes> <EmbedInteropTypes>True</EmbedInteropTypes>
</Reference> </Reference>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="DiffPlex" version="1.7.0" targetFramework="net45" />
<package id="Microsoft.VisualStudio.OLE.Interop" version="16.7.30328.74" targetFramework="net45" /> <package id="Microsoft.VisualStudio.OLE.Interop" version="16.7.30328.74" targetFramework="net45" />
<package id="MimeKitLite" version="2.10.1" targetFramework="net45" /> <package id="MimeKitLite" version="2.10.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
......