using Comal.Classes; using FastReport; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.Core.Reports; using InABox.DynamicGrid; using InABox.Scripting; using InABox.Wpf; using InABox.Wpf.Reports; using InABox.WPF; using PRSDesktop.Configuration; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace PRSDesktop; public interface IPanelHostControl { void ClearActions(); void CreatePanelAction(PanelAction action); void ClearReports(); void CreateReport(PanelAction action); } public class PanelHost : IPanelHost { public IBasePanel? CurrentPanel { get; private set; } public string CurrentModuleName { get; private set; } = ""; private readonly IPanelHostControl HostControl; private readonly List SetupActions = new(); public PanelHost(IPanelHostControl hostControl) { HostControl = hostControl; } #region Module Tracking private int TrackedClicks; private int TrackedKeys; private DateTime TrackedTicks = DateTime.MinValue; public void IncrementTrackingModuleClick() { if (CurrentPanel is not null) TrackedClicks++; } public void IncrementTrackingModuleKey() { if (CurrentPanel is not null) TrackedKeys++; } #endregion #region IPanelHost void IPanelHost.CreatePanelAction(PanelAction action) { HostControl.CreatePanelAction(action); } void IPanelHost.CreateReport(PanelAction action) { HostControl.CreateReport(action); } void IPanelHost.CreateSetupAction(PanelAction action) { SetupActions.Add(action); } void IPanelHost.CreateSetupSeparator() { SetupActions.Add(new PanelActionSeparator()); } #endregion #region Panel Properties private void InitializePanelProperties(IBasePanel panel) { var propertiesInterface = panel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>)); if (propertiesInterface is not null) { var propertiesType = propertiesInterface.GenericTypeArguments[0]; var method = typeof(PanelHost) .GetMethod(nameof(InitializePanelPropertiesGeneric), BindingFlags.NonPublic | BindingFlags.Instance) ?.MakeGenericMethod(panel.GetType(), propertiesType) .Invoke(this, new object?[] { panel }); } } private void InitializePanelPropertiesGeneric(TPanel panel) where TPanel : IPropertiesPanel where TProperties : BaseObject, IGlobalConfigurationSettings, new() { panel.Properties = LoadPanelProperties(); } private TProperties LoadPanelProperties() where TPanel : IPropertiesPanel where TProperties : BaseObject, IGlobalConfigurationSettings, new() { var config = new GlobalConfiguration(); return config.Load(); } private void SavePanelProperties(TProperties properties) where TPanel : IPropertiesPanel where TProperties : BaseObject, IGlobalConfigurationSettings, new() { var config = new GlobalConfiguration(); config.Save(properties); } private void EditPanelProperties() where TPanel : IPropertiesPanel where TProperties : BaseObject, IGlobalConfigurationSettings, new() { var properties = LoadPanelProperties(); bool result; if (DynamicGridUtils.TryFindDynamicGrid(typeof(DynamicGrid<>), typeof(TProperties), out var gridType)) { var grid = (Activator.CreateInstance(gridType) as DynamicGrid)!; result = grid.EditItems(new TProperties[] { properties }); } else { var grid = new DynamicItemsListGrid(); result = grid.EditItems(new TProperties[] { properties }); } if (result) { SavePanelProperties(properties); } } private void ConfigurePanel() { if (CurrentPanel is null) return; var propertiesInterface = CurrentPanel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>))!; var propertiesType = propertiesInterface.GenericTypeArguments[0]; var basemethod = typeof(PanelHost) .GetMethod(nameof(EditPanelProperties), BindingFlags.NonPublic | BindingFlags.Instance); if (basemethod == null) return; var method = basemethod?.MakeGenericMethod(CurrentPanel.GetType(), propertiesType); if (method != null) method.Invoke(this, Array.Empty()); } #endregion #region Actions private void ReloadActions(string sectionName, DataModel model) { SetupActions.Clear(); HostControl.ClearActions(); HostControl.ClearReports(); CreateModules(sectionName, model); if (CurrentPanel != null) { CurrentPanel.CreateToolbarButtons(this); } CreateReports(sectionName, model); } #endregion #region Custom Modules private void CreateModules(string section, DataModel model) { if (ClientFactory.IsSupported()) { foreach (var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(section, model)) { HostControl.CreatePanelAction(new PanelAction { Caption = module.Name ?? "", Image = image, OnExecute = (action) => { ClickModule(action, module); } }); } } } private void ClickModule(PanelAction action, CustomModule code) { if (CurrentPanel != null) { if (!string.IsNullOrWhiteSpace(code.Script)) try { Selection selection; if (code.SelectedRecords && code.AllRecords) selection = RecordSelectionDialog.Execute(); else if (code.SelectedRecords) selection = Selection.Selected; else if (code.AllRecords) selection = Selection.All; else selection = Selection.None; var result = ScriptDocument.RunCustomModule(CurrentPanel.DataModel(selection), CurrentPanel.Selected(), code.Script); if (result) CurrentPanel.Refresh(); } catch (CompileException c) { MessageWindow.ShowError(c.Message, c, shouldLog: false); } catch (Exception err) { MessageWindow.ShowError($"Unable to load {action.Caption}", err); } else MessageWindow.ShowMessage("Unable to load " + action.Caption, "Error", image: MessageWindow.WarningImage); } } private void ManageModules(PanelAction action) { if (CurrentPanel != null) { var section = CurrentPanel.SectionName; var dataModel = CurrentPanel.DataModel(Selection.Selected); var manager = new CustomModuleManager() { Section = section, DataModel = dataModel }; manager.ShowDialog(); ReloadActions(section, dataModel); } } #endregion #region Reports private IEnumerable AddTemplateDefinitions() { if (CurrentPanel is null) return new List() { new ReportExportDefinition("Email Report", PRSDesktop.Resources.email, ReportExportType.PDF, PRSEmailUtils.DoEmailReport)}; else return PRSEmailUtils.CreateTemplateDefinitions(CurrentPanel.DataModel(Selection.None)); } public static PanelAction CreateReportAction(ReportTemplate template, Func getDataModel) { var action = new PanelAction { Caption = template.Name, Image = PRSDesktop.Resources.printer, OnExecute = (action) => { PrintReport(template.ID, getDataModel); } }; if (Security.IsAllowed()) { var menu = new ContextMenu(); menu.AddItem("Design Report", PRSDesktop.Resources.pencil, () => DesignReport(template.ID, getDataModel)); action.Menu = menu; } return action; } private void CreateReports(string section, DataModel model) { if (CurrentPanel is null) return; var client = new Client(); var templates = ReportUtils.LoadReports(section, model, new Columns(x => x.ID, x => x.Name)); foreach (var template in templates) { HostControl.CreateReport(CreateReportAction(template, CurrentPanel.DataModel)); } } private static void DesignReport(Guid templateID, Func getDataModel) { var template = new Client().Load(new Filter(x => x.ID).IsEqualTo(templateID)).FirstOrDefault(); if (template is null) { Logger.Send(LogType.Error, "", $"No Report Template with ID '{templateID}'"); MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage); return; } ReportUtils.DesignReport(template, getDataModel(Selection.None)); } private static void PrintReport(Guid id, Func getDataModel) { var template = new Client().Load(new Filter(x => x.ID).IsEqualTo(id)).FirstOrDefault(); if (template == null) { Logger.Send(LogType.Error, "", $"No Report Template with ID '{id}'"); MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage); return; } var selection = Selection.None; if (template.SelectedRecords && template.AllRecords) selection = RecordSelectionDialog.Execute(); else if (template.SelectedRecords) selection = Selection.Selected; else if (template.AllRecords) selection = Selection.All; else MessageWindow.ShowMessage( "Report must have either [Selected Records] or [All Records] checked to display!", "Error", image: MessageWindow.WarningImage); if (selection != Selection.None) ReportUtils.PreviewReport(template, getDataModel(selection), false, Security.IsAllowed()); } private void ManageReports(PanelAction action) { if (CurrentPanel is null) return; var section = CurrentPanel.SectionName; var model = CurrentPanel.DataModel(Selection.None); if (model == null) { MessageWindow.ShowMessage("No DataModel for " + CurrentPanel.SectionName, "No DataModel"); return; } var form = new ReportManager { DataModel = model, Section = section, Populate = true }; form.ShowDialog(); ReloadActions(section, model); } private void ManageEmailTemplates(PanelAction action) { if (CurrentPanel is null) return; var section = CurrentPanel.SectionName; var model = CurrentPanel.DataModel(Selection.None); if (model == null) { MessageWindow.ShowMessage("No DataModel for " + section, "No DataModel"); return; } var window = new EmailTemplateManagerWindow(model); window.ShowDialog(); } #endregion #region Public Interface public void InitialiseSetupMenu(ContextMenu menu) { var items = new List(); items.AddRange(SetupActions); items.Add(new PanelActionSeparator()); if (Security.IsAllowed()) { items.Add(new PanelAction("Custom Modules", PRSDesktop.Resources.script, ManageModules)); } if (Security.IsAllowed()) { items.Add(new PanelAction("Reports", PRSDesktop.Resources.printer, ManageReports)); } if (Security.IsAllowed()) { items.Add(new PanelAction("Email Templates", PRSDesktop.Resources.email, ManageEmailTemplates)); } for (var i = 0; i < items.Count; ++i) { var item = items[i]; if (item is PanelAction setupAction) { menu.AddItem(setupAction.Caption, setupAction.Image, setupAction, setupAction.OnExecute); } else if (item is PanelActionSeparator && i > 0 && i < items.Count - 1) { var last = items[i - 1]; if (last is not PanelActionSeparator) menu.AddSeparator(); } } if (CurrentPanel?.GetType().HasInterface(typeof(IPropertiesPanel<>)) == true && Security.IsAllowed()) { var securityInterface = CurrentPanel?.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<,>)); var canConfigure = false; if (securityInterface is not null) { var token = securityInterface.GenericTypeArguments[1]; canConfigure = Security.IsAllowed(token); } else { canConfigure = Security.IsAllowed(); } if (canConfigure) { menu.AddItem("Configure Panel", PRSDesktop.Resources.edit, ConfigurePanel); } } if (menu.Items.Count == 0) { menu.AddItem("No Items", null, null, false); } } public IBasePanel LoadPanel(Type T, string moduleName) { return (LoadPanelMethod.MakeGenericMethod(T).Invoke(this, new object[] { moduleName }) as IBasePanel)!; } private static readonly MethodInfo LoadPanelMethod = typeof(PanelHost) .GetMethods().First(x => x.Name == nameof(LoadPanel) && x.IsGenericMethod); public T LoadPanel(string moduleName) where T : class, IBasePanel, new() { var panel = new T(); CurrentPanel = panel; ReportUtils.ExportDefinitions.Clear(); ReportUtils.ExportDefinitions.AddRange(AddTemplateDefinitions()); InitializePanelProperties(panel); CurrentModuleName = moduleName; TrackedTicks = DateTime.Now; CurrentPanel.IsReady = false; CurrentPanel.Setup(); CurrentPanel.IsReady = true; CurrentPanel.OnUpdateDataModel += ReloadActions; var model = CurrentPanel.DataModel(Selection.None); var section = CurrentPanel.SectionName; ReloadActions(section, model); return panel; } public void Refresh() { CurrentPanel?.Refresh(); } private void Heartbeat(TimeSpan time, bool closing) { if (!closing && time.TotalMinutes < 5) return; TrackedTicks = DateTime.Now; if (CurrentPanel is not null) { //Logger.Send(LogType.Information, "", string.Format("Heartbeat: {0}", CurrentPanel_Label)); if (ClientFactory.IsSupported()) { var keys = TrackedKeys; TrackedKeys = 0; var clicks = TrackedClicks; TrackedClicks = 0; var tracking = new ModuleTracking { Date = DateTime.Today, Module = CurrentModuleName, Clicks = clicks, Keys = keys, ActiveTime = clicks + keys > 0 ? time : new TimeSpan(), IdleTime = clicks + keys == 0 ? time : new TimeSpan() }; tracking.User.ID = ClientFactory.UserGuid; new Client().Save(tracking, "", (mt, ex) => { }); } CurrentPanel.Heartbeat(time); } } public void Heartbeat() { Heartbeat(DateTime.Now - TrackedTicks, false); } public void UnloadPanel(CancelEventArgs? cancel) { if (CurrentPanel != null) { Heartbeat(DateTime.Now - TrackedTicks, true); try { CurrentPanel.Shutdown(cancel); if (cancel?.Cancel == true) { return; } } catch (Exception e) { Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Error in UnloadPanel(): {0}\n{1}", e.Message, e.StackTrace)); } TrackedTicks = DateTime.MinValue; CurrentModuleName = ""; TrackedClicks = 0; TrackedKeys = 0; } } #endregion }