Commits (5)
......@@ -28,14 +28,15 @@ namespace pEp.DPE
if (context.Request.HttpMethod != "POST")
{
Log.Warning("Process request: Ignoring request of type " + context.Request.HttpMethod);
return;
}
string request = null;
try
{
using (var stream = context.Request.InputStream)
using (var sr = new StreamReader(stream))
using (Stream stream = context.Request.InputStream)
using (StreamReader sr = new StreamReader(stream))
{
request = sr.ReadToEnd();
}
......@@ -43,6 +44,8 @@ namespace pEp.DPE
catch (Exception ex)
{
Log.Error("ProcessRequest: Error getting request. " + ex.ToString());
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Response.Close();
return;
}
......
......@@ -10,8 +10,8 @@ namespace pEp.DPE
{
internal class DistributedPolicyEngine : IDistributedPolicyEngine
{
private readonly static string DPE_FOLDER = Path.Combine(Globals.PEPUserFolder, "DPE");
public readonly static string DPE_TEMP_LOCATION = Path.Combine(DistributedPolicyEngine.DPE_FOLDER, "temp");
private static readonly string DPE_FOLDER = Path.Combine(Globals.PEPUserFolder, "DPE");
public static readonly string DPE_TEMP_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";
......@@ -127,7 +127,6 @@ namespace pEp.DPE
public void Reject(Patch patch, PEPIdentity me)
{
DPEWebClient.RejectPatch(patch, me);
this.DeletePatch(patch);
AdapterExtensions.ShowNotification("Patch rejected", patch.CommitMessage);
}
......@@ -137,9 +136,9 @@ namespace pEp.DPE
/// <param name="patchEvents">The patch events to subscribe to.</param>
public void Subscribe(PatchEvents patchEvents)
{
patchEvents.PatchAccepted += PatchEvents_PatchAccepted;
patchEvents.PatchRejected += PatchEvents_PatchRejected;
patchEvents.PatchSuggested += PatchEvents_PatchSuggested;
patchEvents.PatchAccepted += this.PatchEvents_PatchAccepted;
patchEvents.PatchRejected += this.PatchEvents_PatchRejected;
patchEvents.PatchSuggested += this.PatchEvents_PatchSuggested;
}
/// <summary>
......@@ -161,7 +160,6 @@ namespace pEp.DPE
public void Support(Patch patch, PEPIdentity me)
{
DPEWebClient.SupportPatch(patch, me);
this.DeletePatch(patch);
AdapterExtensions.ShowNotification("Patch supported", patch.CommitMessage);
}
......@@ -171,67 +169,9 @@ namespace pEp.DPE
/// <param name="patchEvents">The patch events to unsubscribe from.</param>
public void Unsubscribe(PatchEvents patchEvents)
{
patchEvents.PatchAccepted -= PatchEvents_PatchAccepted;
patchEvents.PatchRejected -= PatchEvents_PatchRejected;
patchEvents.PatchSuggested -= PatchEvents_PatchSuggested;
}
/// <summary>
/// Deletes the patch from the disk.
/// </summary>
/// <param name="patch">The patch to delete.</param>
/// <returns>True if the patch was deleted or not found, otherwise false.</returns>
private bool DeletePatch(Patch patch)
{
try
{
string fileName = Path.Combine(DistributedPolicyEngine.DPE_FOLDER, patch.Id + DistributedPolicyEngine.PATCH_EXTENSION);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
return true;
}
catch (Exception ex)
{
Log.Error("DeletePatch: Error occured. " + ex.ToString());
}
return false;
}
#endregion
#region Static methods
/// <summary>
/// Gets the patches that are currently saved to disk.
/// </summary>
/// <returns>The list of open patches or an empty list if none was found.</returns>
internal static List<Tuple<Patch, bool>> GetOpenPatches()
{
List<Tuple<Patch, bool>> patches = new List<Tuple<Patch, bool>>();
try
{
foreach (string file in Directory.GetFiles(DistributedPolicyEngine.DPE_FOLDER, "*" + DistributedPolicyEngine.PATCH_EXTENSION))
{
string xml = File.ReadAllText(file);
if ((Patch.Deserialize(xml) is Patch patch) &&
(patch != null))
{
patches.Add(new Tuple<Patch, bool>(patch, false));
}
}
}
catch (Exception ex)
{
Log.Error("GetPatches: Error getting patches. " + ex.ToString());
}
return patches;
patchEvents.PatchAccepted -= this.PatchEvents_PatchAccepted;
patchEvents.PatchRejected -= this.PatchEvents_PatchRejected;
patchEvents.PatchSuggested -= this.PatchEvents_PatchSuggested;
}
#endregion
......
......@@ -17,16 +17,18 @@ namespace pEp
/// </summary>
internal static class MailItemExtensions
{
public const string USER_PROPERTY_KEY_ORIG_ENTRY_ID = "origEntryID";
public const string USER_PROPERTY_KEY_DPE_PATCH_STATUS = "patchStatus";
public const string USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE = "patchEditDate";
public const string USER_PROPERTY_KEY_INSPECTOR_CLOSED = "inspectorClosed";
public const string USER_PROPERTY_KEY_IS_INCOMING = "isIncoming";
public const string USER_PROPERTY_KEY_IS_MIRROR = "isMirror";
public const string USER_PROPERTY_KEY_ORIG_ENTRY_ID = "origEntryID";
public const string USER_PROPERTY_KEY_PROCESSING_STATE = "processingState";
public const string USER_PROPERTY_KEY_REPLY_ACTION = "replyAction";
public const string UNKNOWN_SENDER = "unknown";
private static readonly object mutexCopiedItemsList = new object();
private static List<string> copiedItemsList = new List<string>();
private static readonly List<string> copiedItemsList = new List<string>();
/// <summary>
/// Enumeration defining the standard, setable pEp properties of an extended MailItem.
......@@ -1190,7 +1192,6 @@ namespace pEp
/// <returns>True if it is an attached mail, otherwise false.</returns>
public static bool GetIsAttachedMail(this Outlook.MailItem omi, out string messageId)
{
messageId = null;
HeaderList headers = omi.GetParsedTransportMessageHeaders();
try
......
......@@ -1614,7 +1614,6 @@ namespace pEp
// TODO: Check whether this method should be part of the engine?
int unencryptedCount = 0;
pEpRating rating = pEpRating.pEpRatingUndefined;
List<PEPIdentity> forceUnencryptedList = new List<PEPIdentity>();
PEPIdentity[] recipients;
PEPMessage workingMessage;
......
using pEp.DPE;
using pEp.UI.Models;
using pEp.UI.ViewModels;
using System;
using System.ComponentModel;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace pEp
{
partial class FormRegionDPE
internal partial class FormRegionDPE
{
#region Form Region Factory
......@@ -33,10 +33,41 @@ namespace pEp
try
{
omi = this.OutlookItem as Outlook.MailItem;
string xml = omi.HTMLBody;
Patch patch = Patch.Deserialize(xml);
PEPIdentity.GetFromIdentity(omi, out PEPIdentity submitter);
this.FormControlPatchView.DataContext = new FormControlPatchViewModel(patch, submitter);
FormControlPatchViewModel.PatchStatus patchStatus = FormControlPatchViewModel.PatchStatus.Open;
DateTime? editDate = null;
// Get patch status if available
if ((omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_STATUS, FormControlPatchViewModel.PatchStatus.Open) is string patchStatusString) &&
Enum.TryParse(patchStatusString, out FormControlPatchViewModel.PatchStatus status))
{
patchStatus = status;
}
// Get last edit date if available
if ((omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE) is string editDateString) &&
DateTime.TryParse(editDateString, out DateTime savedEditDate))
{
editDate = savedEditDate;
}
// Get the patch submitter
if (PEPIdentity.GetFromIdentity(omi, out PEPIdentity submitter) == Globals.ReturnStatus.Success)
{
this.FormControlPatchView.DataContext = new FormControlPatchViewModel(patch, submitter, patchStatus, editDate);
// Subscribe to property changed event
if (this.FormControlPatchView.DataContext is FormControlPatchViewModel formControlPatchViewModel)
{
formControlPatchViewModel.PropertyChanged += this.FormRegionDPE_PropertyChanged;
}
}
else
{
throw new Exception("FormRegionDPE_FormRegionShowing: Error getting patch submitter.");
}
}
catch (Exception ex)
{
......@@ -48,11 +79,55 @@ namespace pEp
}
}
/// <summary>
/// Event handler for when a property in the associated view model changes.
/// </summary>
private void FormRegionDPE_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Set patch status and edit date as user properties if being changed
if ((e.PropertyName == nameof(FormControlPatchViewModel.Status)) ||
(e.PropertyName == nameof(FormControlPatchViewModel.EditDate)))
{
Outlook.MailItem omi = null;
try
{
omi = this.OutlookItem as Outlook.MailItem;
FormControlPatchViewModel.PatchStatus patchStatus = (sender as FormControlPatchViewModel).Status;
DateTime? editDate = (sender as FormControlPatchViewModel).EditDate;
if (patchStatus != FormControlPatchViewModel.PatchStatus.Open)
{
omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_STATUS, Enum.GetName(typeof(FormControlPatchViewModel.PatchStatus), patchStatus));
}
if (editDate != null)
{
omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE, editDate?.ToString("f"));
}
omi.Save();
}
catch (Exception ex)
{
Log.Error("FormRegionDPE_PropertyChanged: Error setting user property. " + ex);
}
finally
{
omi = null;
}
}
}
// Occurs when the form region is closed.
// Use this.OutlookItem to get a reference to the current Outlook item.
// Use this.OutlookFormRegion to get a reference to the form region.
private void FormRegionDPE_FormRegionClosed(object sender, EventArgs e)
{
// Unsubscribe from property changed event
if (this.FormControlPatchView.DataContext is FormControlPatchViewModel formControlPatchViewModel)
{
formControlPatchViewModel.PropertyChanged -= this.FormRegionDPE_PropertyChanged;
}
}
}
}
using pEp.DPE;
using System;
using System.Windows.Documents;
using System.Windows.Media;
namespace pEp.UI.ViewModels
{
internal class FormControlPatchViewModel : ViewModelBase
{
public enum PatchStatus
{
Open,
Accepted,
Supported,
Rejected
}
#region Fields
private Patch patch;
private readonly Patch patch;
private PatchStatus _Status = PatchStatus.Open;
private DateTime? _EditDate = null;
private string _Explanation = null;
#endregion
......@@ -48,12 +60,17 @@ namespace pEp.UI.ViewModels
/// <summary>
/// Gets the diff of this patch as formatted flow document.
/// </summary>
public FlowDocument DisplayDiff { get; }
public FlowDocument DisplayDiff => PatchDialogViewModel.FormatDiff(this.Diff);
/// <summary>
/// Gets or sets the last date the patch was edited.
/// </summary>
public DateTime? EditDate { get => this._EditDate; set => this.SetProperty(ref this._EditDate, value); }
/// <summary>
/// The explanation shown in the UI regarding this patch.
/// </summary>
public string Explanation { get; }
public string Explanation { get => this._Explanation; set => this.SetProperty(ref this._Explanation, value); }
/// <summary>
/// Gets whether the OK button is visible.
......@@ -68,7 +85,7 @@ namespace pEp.UI.ViewModels
/// <summary>
/// The command to accept the patch dialog.
/// </summary>
public RelayCommand OKButtonCommand => new RelayCommand(this.SupportPatch);
public RelayCommand OKButtonCommand => new RelayCommand(this.SupportPatch, p => (this.Status == PatchStatus.Open));
/// <summary>
/// Gets the OK button text.
......@@ -78,7 +95,7 @@ namespace pEp.UI.ViewModels
/// <summary>
/// The command to reject the dialog.
/// </summary>
public RelayCommand RejectButtonCommand => new RelayCommand(this.RejectPatch);
public RelayCommand RejectButtonCommand => new RelayCommand(this.RejectPatch, p => (this.Status == PatchStatus.Open));
/// <summary>
/// Gets the Reject button text.
......@@ -90,6 +107,11 @@ namespace pEp.UI.ViewModels
/// </summary>
public PEPIdentity Submitter { get; }
/// <summary>
/// Gets or sets the status of this patch.
/// </summary>
public PatchStatus Status { get => this._Status; set => this.SetProperty(ref this._Status, value); }
/// <summary>
/// Gets or sets the tag of the patch.
/// </summary>
......@@ -125,14 +147,17 @@ namespace pEp.UI.ViewModels
/// </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)
/// <param name="status">The status of the patch.</param>
/// <param name="editDate">The last time this patch was edited (accepted/supported/rejected).</param>
public FormControlPatchViewModel(Patch patch, PEPIdentity submitter, PatchStatus status = PatchStatus.Open, DateTime? editDate = null)
{
this.EditDate = editDate;
this.patch = patch;
this.Submitter = submitter;
this.Explanation = "New configuration changes pending approval";
this.Status = status;
this.IsRejectButtonVisible = true;
this.OKButtonText = "Support";
this.DisplayDiff = this.FormatDiff();
this.SetExplanation();
}
#endregion
......@@ -140,58 +165,44 @@ namespace pEp.UI.ViewModels
#region Methods
/// <summary>
/// Formats the diff displaying colors of changes.
/// Rejects this patch.
/// </summary>
/// <returns>The formatted diff or null if no diff is available.</returns>
private FlowDocument FormatDiff()
/// <param name="parameter">The command parameter.</param>
private void RejectPatch(object parameter)
{
if (string.IsNullOrEmpty(this.Diff))
{
return null;
}
FlowDocument document = new FlowDocument
{
FontFamily = new FontFamily("Courier New"),
FontSize = 12.0,
Background = Brushes.White
};
string[] lines = this.Diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { };
foreach (string line in lines)
{
Paragraph paragraph = new Paragraph(new Run(line))
{
Margin = new System.Windows.Thickness(1),
LineHeight = 14.0
};
if (line.StartsWith("-"))
{
paragraph.Background = Globals.ResourceDict["BrushRedBackground"] as Brush;
}
else if (line.StartsWith("+"))
{
paragraph.Background = Globals.ResourceDict["BrushGreenBackground"] as Brush;
}
else if (line.StartsWith("@@"))
{
paragraph.Background = Brushes.LightGray;
}
document.Blocks.Add(paragraph);
}
return document;
Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity());
this.Status = PatchStatus.Rejected;
this.EditDate = DateTime.UtcNow;
this.SetExplanation();
}
/// <summary>
/// Rejects this patch.
/// Sets the explanation text.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private void RejectPatch(object parameter)
private void SetExplanation()
{
Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity());
string editDate = "<n/a>";
if (this.EditDate != null)
{
editDate = ((DateTime)this.EditDate).ToLocalTime().ToString("F");
}
switch (this.Status)
{
case PatchStatus.Accepted:
this.Explanation = $"Accepted on { editDate }";
break;
case PatchStatus.Supported:
this.Explanation = $"Supported on { editDate }";
break;
case PatchStatus.Rejected:
this.Explanation = $"Rejected on { editDate }";
break;
case PatchStatus.Open:
default:
this.Explanation = "New configuration changes pending approval";
break;
}
}
/// <summary>
......@@ -201,6 +212,9 @@ namespace pEp.UI.ViewModels
private void SupportPatch(object parameter)
{
Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity());
this.Status = PatchStatus.Supported;
this.EditDate = DateTime.UtcNow;
this.SetExplanation();
}
#endregion
......
......@@ -31,7 +31,7 @@ namespace pEp.UI.ViewModels
MessageGroups = 5
}
private PEPSettings pEpSettings;
private readonly PEPSettings pEpSettings;
private int _AccountSettingsListSelectedIndex = -1;
private RelayCommand _CommandButtonAddGroup = null;
......@@ -45,9 +45,6 @@ namespace pEp.UI.ViewModels
private RelayCommand _CommandButtonRefreshLogs = null;
private RelayCommand _CommandButtonResetPEPStore = null;
private RelayCommand _CommandButtonResetAllOwnKeys = null;
private RelayCommand<IList<object>> _CommandOpenPatch = null;
private RelayCommand<IList<object>> _CommandRejectPatch = null;
private RelayCommand<IList<object>> _CommandSupportPatch = null;
private bool _IsAdvancedEnabled = false;
private string _LogEngine = null;
private string _LogOutlook = null;
......@@ -78,12 +75,6 @@ namespace pEp.UI.ViewModels
this.AccountSettingsList.Add(new AccountViewModel(accountSettings, this.CalculateDependentProperties));
});
// Add patches
DistributedPolicyEngine.GetOpenPatches()?.ForEach((tuple) =>
{
this.Patches.Add(new PatchViewModel(tuple.Item1, tuple.Item2));
});
// Calculate dependent properties
this.calcDepPropIsEnabled = true;
this.CalculateDependentProperties();
......@@ -305,55 +296,6 @@ namespace pEp.UI.ViewModels
}
}
/// <summary>
/// Gets the command to open a patch.
/// </summary>
public RelayCommand<IList<object>> CommandOpenPatch
{
get
{
if (this._CommandOpenPatch == null)
{
this._CommandOpenPatch = new RelayCommand<IList<object>>(p => (p?.First() as PatchViewModel)?.OpenPatchDialog(PatchDialog.PatchAction.SupportOrRejectPatch));
}
return this._CommandOpenPatch;
}
}
/// <summary>
/// Gets the command to reject a patch.
/// </summary>
public RelayCommand<IList<object>> CommandRejectPatch
{
get
{
if (this._CommandRejectPatch == null)
{
this._CommandRejectPatch = new RelayCommand<IList<object>>(p => this.RejectPatch(p?.First() as PatchViewModel));
}
return this._CommandRejectPatch;
}
}
/// <summary>
/// Gets the command to support a patch.
/// </summary>
public RelayCommand<IList<object>> CommandSupportPatch
{
get
{
if (this._CommandSupportPatch == null)
{
this._CommandSupportPatch = new RelayCommand<IList<object>>(p => this.SupportPatch(p?.First() as PatchViewModel));
}
return this._CommandSupportPatch;
}
}
/// <summary>
/// Gets the list of projects that are used in pEp for Outlook and that
/// are being credited.
......@@ -1113,37 +1055,6 @@ namespace pEp.UI.ViewModels
} while (retry);
}
/// <summary>
/// Opens the Patch dialog.
/// </summary>
/// <param name="patchViewModel">The patch view model that </param>
/// <param name="patchAction">The patch action to execute.</param>
/// <returns>The dialog result.</returns>
public System.Windows.Forms.DialogResult OpenPatchDialog(PatchViewModel patchViewModel, PatchDialog.PatchAction patchAction)
{
if (patchViewModel == null)
{
Log.ErrorAndFailInDebugMode("OpenPatchDialog: patch view model is null.");
}
System.Windows.Forms.DialogResult dialogResult = patchViewModel?.OpenPatchDialog(patchAction) ?? System.Windows.Forms.DialogResult.Cancel;
if (dialogResult != System.Windows.Forms.DialogResult.Cancel)
{
this.Patches.Clear();
// Add patches
DistributedPolicyEngine.GetOpenPatches()?.ForEach((tuple) =>
{
this.Patches.Add(new PatchViewModel(tuple.Item1, tuple.Item2));
});
this.OnPropertyChanged(nameof(this.Patches));
}
return dialogResult;
}
/// <summary>
/// Resets the logs.
/// </summary>
......@@ -1179,43 +1090,6 @@ namespace pEp.UI.ViewModels
}
}
/// <summary>
/// Removes the given patch from the list of patches.
/// </summary>
/// <param name="patchViewModel">The patch view model to remove.</param>
private void RemovePatch(PatchViewModel patchViewModel)
{
if (patchViewModel == null)
{
Log.ErrorAndFailInDebugMode("RemovePatch: patch view model is null.");
}
if (!this.Patches.Remove(patchViewModel))
{
Log.Error("RemovePatch: Could not remove patch.");
}
}
/// <summary>
/// Rejects the patch.
/// </summary>
/// <param name="patchViewModel">The patch's view model.</param>
private void RejectPatch(PatchViewModel patchViewModel)
{
patchViewModel?.RejectPatch();
this.RemovePatch(patchViewModel);
}
/// <summary>
/// Supports the patch.
/// </summary>
/// <param name="patchViewModel">The patch's view model.</param>
private void SupportPatch(PatchViewModel patchViewModel)
{
patchViewModel?.SupportPatch();
this.RemovePatch(patchViewModel);
}
#endregion
}
}
......@@ -10,6 +10,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
......@@ -372,40 +373,30 @@ namespace pEp.UI.ViewModels
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)
if (newLines[i].Type == ChangeType.Imaginary)
{
block.Add("-" + oldLines[i].Text);
unchangedCount = 0;
}
else if (newLines[i].Type == ChangeType.Inserted)
{
block.Add("+" + newLines[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
// First, add the three preceding unchanged lines (if available)
if (newBlock)
{
newBlock = false;
......@@ -425,14 +416,32 @@ namespace pEp.UI.ViewModels
newFileIndex = newLines[index].Position ?? 1;
oldFileIndex = oldLines[index].Position ?? 1;
}
else if ((unchangedCount > 2) || (i == newLines.Count - 1))
// Then, add the three following lines (if available)
int counter = 0;
while ((++i < newLines.Count) &&
(newLines[i].Type == ChangeType.Unchanged))
{
if (counter++ < 3)
{
block.Add(" " + newLines[i].Text);
}
else
{
break;
}
}
// Close block
if ((counter > 3) ||
(i >= newLines.Count))
{
// 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)
foreach (string line in block)
{
diff += line + '\n';
}
......@@ -440,6 +449,8 @@ namespace pEp.UI.ViewModels
block = new List<string>();
newBlock = true;
}
i--;
}
}
......@@ -512,53 +523,6 @@ namespace pEp.UI.ViewModels
Log.Error($"EditFileAndCreateDiff: Error reading file content from { fileName }: { ex }");
}
}
/// <summary>
/// Formats the diff displaying colors of changes.
/// </summary>
/// <returns>The formatted diff or null if no diff is available.</returns>
private FlowDocument FormatDiff(string diff)
{
if (string.IsNullOrEmpty(diff))
{
return null;
}
FlowDocument document = new FlowDocument
{
FontFamily = new FontFamily("Courier New"),
FontSize = 12.0,
Background = Brushes.White,
};
string[] lines = diff?.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { };
foreach (string line in lines)
{
Paragraph paragraph = new Paragraph(new Run(line))
{
Margin = new System.Windows.Thickness(1),
LineHeight = 14.0
};
if (line.StartsWith("-"))
{
paragraph.Background = Globals.ResourceDict["BrushRedBackground"] as Brush;
}
else if (line.StartsWith("+"))
{
paragraph.Background = Globals.ResourceDict["BrushGreenBackground"] as Brush;
}
else if (line.StartsWith("@@"))
{
paragraph.Background = Brushes.LightGray;
}
document.Blocks.Add(paragraph);
}
return document;
}
/// <summary>
/// Gets a common root URI from all selected files in the dialog.
/// </summary>
......@@ -620,7 +584,7 @@ namespace pEp.UI.ViewModels
/// </summary>
private void UpdateVisibleDiff()
{
this.VisibleDiff = this.FormatDiff(this.SelectedFile?.Diff);
this.VisibleDiff = PatchDialogViewModel.FormatDiff(this.SelectedFile?.Diff);
}
/// <summary>
......@@ -635,5 +599,73 @@ namespace pEp.UI.ViewModels
}
#endregion
#region Static methods
/// <summary>
/// Formats the diff displaying colors of changes.
/// </summary>
/// <returns>The formatted diff or null if no diff is available.</returns>
public static FlowDocument FormatDiff(string diff)
{
if (string.IsNullOrEmpty(diff))
{
return null;
}
// Initialize the document with zero width to set the real width later
FlowDocument document = new FlowDocument
{
Background = Brushes.White,
PageWidth = 0
};
string[] lines = diff.Replace("\r\n", "\n")?.Split('\n') ?? new string[] { };
foreach (string line in lines)
{
Paragraph paragraph = new Paragraph()
{
Margin = new Thickness(1),
};
TextBlock textBlock = new TextBlock()
{
Text = line,
FontFamily = new FontFamily("Courier New"),
LineHeight = 14.0,
FontSize = 12.0,
TextWrapping = TextWrapping.NoWrap
};
// Set the document width to the biggest textblock width plus some margin
// to account for padding inside the scroll viewer
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (textBlock.DesiredSize.Width > document.PageWidth)
{
document.PageWidth = textBlock.DesiredSize.Width + 50;
}
paragraph.Inlines.Add(textBlock);
// Color the added and removed lines accordingly
if (line.StartsWith("-"))
{
paragraph.Background = Globals.ResourceDict["BrushRedBackground"] as Brush;
}
else if (line.StartsWith("+"))
{
paragraph.Background = Globals.ResourceDict["BrushGreenBackground"] as Brush;
}
else if (line.StartsWith("@@"))
{
paragraph.Background = Brushes.LightGray;
}
document.Blocks.Add(paragraph);
}
return document;
}
#endregion
}
}
......@@ -72,6 +72,7 @@
<FlowDocumentScrollViewer Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="3"
HorizontalScrollBarVisibility="Auto"
BorderBrush="Black"
BorderThickness="1"
Margin="5,10,10,10"
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace pEp.UI.Views
{
......
......@@ -976,52 +976,7 @@
FontSize="16" />
<Separator />
<!--Patch management-->
<ListBox Name="patchManagementListBox"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Patches}"
MinHeight="300"
Margin="10,5" >
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Open"
Command="{Binding DataContext.CommandOpenPatch}"
CommandParameter="{Binding Path=SelectedItems}"/>
<MenuItem Header="Support"
Command="{Binding DataContext.CommandSupportPatch}"
CommandParameter="{Binding Path=SelectedItems}"/>
<MenuItem Header="Reject"
Command="{Binding DataContext.CommandRejectPatch}"
CommandParameter="{Binding Path=SelectedItems}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl MouseDoubleClick="ContentControl_MouseDoubleClick">
<Grid Background="{Binding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding CommitMessage}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
Foreground="{Binding Foreground}"
Margin="10"/>
<TextBlock Grid.Column="1"
Text="{Binding LastUpdate}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
Foreground="{Binding Foreground}"
Margin="10"/>
</Grid>
</ContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--New patch button-->
<Button Content="New patch"
Style="{StaticResource StyleButtonGray}"
Margin="10,5,5,5"
......
......@@ -72,6 +72,7 @@
BorderBrush="Black"
BorderThickness="1"
Margin="5,10,10,10"
HorizontalScrollBarVisibility="Auto"
Document="{Binding VisibleDiff}"/>
<ui:TextBoxWithPlaceholder Grid.Column="0"
Grid.ColumnSpan="3"
......