PanelHost.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. using Comal.Classes;
  2. using FastReport;
  3. using InABox.Clients;
  4. using InABox.Configuration;
  5. using InABox.Core;
  6. using InABox.Core.Reports;
  7. using InABox.DynamicGrid;
  8. using InABox.Scripting;
  9. using InABox.Wpf;
  10. using InABox.Wpf.Reports;
  11. using InABox.WPF;
  12. using PRSDesktop.Configuration;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.ComponentModel;
  16. using System.Drawing;
  17. using System.Linq;
  18. using System.Reflection;
  19. using System.Text;
  20. using System.Threading.Tasks;
  21. using System.Windows;
  22. using System.Windows.Controls;
  23. namespace PRSDesktop;
  24. public interface IPanelHostControl
  25. {
  26. void ClearActions();
  27. void CreatePanelAction(PanelAction action);
  28. void ClearReports();
  29. void CreateReport(PanelAction action);
  30. }
  31. public class PanelHost : IPanelHost
  32. {
  33. public IBasePanel? CurrentPanel { get; private set; }
  34. public string CurrentModuleName { get; private set; } = "";
  35. private readonly IPanelHostControl HostControl;
  36. private readonly List<IPanelActionItem> SetupActions = new();
  37. public PanelHost(IPanelHostControl hostControl)
  38. {
  39. HostControl = hostControl;
  40. }
  41. #region Module Tracking
  42. private int TrackedClicks;
  43. private int TrackedKeys;
  44. private DateTime TrackedTicks = DateTime.MinValue;
  45. public void IncrementTrackingModuleClick()
  46. {
  47. if (CurrentPanel is not null)
  48. TrackedClicks++;
  49. }
  50. public void IncrementTrackingModuleKey()
  51. {
  52. if (CurrentPanel is not null)
  53. TrackedKeys++;
  54. }
  55. #endregion
  56. #region IPanelHost
  57. void IPanelHost.CreatePanelAction(PanelAction action)
  58. {
  59. HostControl.CreatePanelAction(action);
  60. }
  61. void IPanelHost.CreateReport(PanelAction action)
  62. {
  63. HostControl.CreateReport(action);
  64. }
  65. void IPanelHost.CreateSetupAction(PanelAction action)
  66. {
  67. SetupActions.Add(action);
  68. }
  69. void IPanelHost.CreateSetupSeparator()
  70. {
  71. SetupActions.Add(new PanelActionSeparator());
  72. }
  73. #endregion
  74. #region Panel Properties
  75. private void InitializePanelProperties(IBasePanel panel)
  76. {
  77. var propertiesInterface = panel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>));
  78. if (propertiesInterface is not null)
  79. {
  80. var propertiesType = propertiesInterface.GenericTypeArguments[0];
  81. var method = typeof(PanelHost)
  82. .GetMethod(nameof(InitializePanelPropertiesGeneric), BindingFlags.NonPublic | BindingFlags.Instance)
  83. ?.MakeGenericMethod(panel.GetType(), propertiesType)
  84. .Invoke(this, new object?[] { panel });
  85. }
  86. }
  87. private void InitializePanelPropertiesGeneric<TPanel, TProperties>(TPanel panel)
  88. where TPanel : IPropertiesPanel<TProperties>
  89. where TProperties : BaseObject, IGlobalConfigurationSettings, new()
  90. {
  91. panel.Properties = LoadPanelProperties<TPanel, TProperties>();
  92. }
  93. private TProperties LoadPanelProperties<TPanel, TProperties>()
  94. where TPanel : IPropertiesPanel<TProperties>
  95. where TProperties : BaseObject, IGlobalConfigurationSettings, new()
  96. {
  97. var config = new GlobalConfiguration<TProperties>();
  98. return config.Load();
  99. }
  100. private void SavePanelProperties<TPanel, TProperties>(TProperties properties)
  101. where TPanel : IPropertiesPanel<TProperties>
  102. where TProperties : BaseObject, IGlobalConfigurationSettings, new()
  103. {
  104. var config = new GlobalConfiguration<TProperties>();
  105. config.Save(properties);
  106. }
  107. private void EditPanelProperties<TPanel, TProperties>()
  108. where TPanel : IPropertiesPanel<TProperties>
  109. where TProperties : BaseObject, IGlobalConfigurationSettings, new()
  110. {
  111. var properties = LoadPanelProperties<TPanel, TProperties>();
  112. bool result;
  113. if (DynamicGridUtils.TryFindDynamicGrid(typeof(DynamicGrid<>), typeof(TProperties), out var gridType))
  114. {
  115. var grid = (Activator.CreateInstance(gridType) as DynamicGrid<TProperties>)!;
  116. result = grid.EditItems(new TProperties[] { properties });
  117. }
  118. else
  119. {
  120. var grid = new DynamicItemsListGrid<TProperties>();
  121. result = grid.EditItems(new TProperties[] { properties });
  122. }
  123. if (result)
  124. {
  125. SavePanelProperties<TPanel, TProperties>(properties);
  126. }
  127. }
  128. private void ConfigurePanel()
  129. {
  130. if (CurrentPanel is null) return;
  131. var propertiesInterface = CurrentPanel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>))!;
  132. var propertiesType = propertiesInterface.GenericTypeArguments[0];
  133. var basemethod = typeof(PanelHost)
  134. .GetMethod(nameof(EditPanelProperties), BindingFlags.NonPublic | BindingFlags.Instance);
  135. if (basemethod == null)
  136. return;
  137. var method = basemethod?.MakeGenericMethod(CurrentPanel.GetType(), propertiesType);
  138. if (method != null)
  139. method.Invoke(this, Array.Empty<object?>());
  140. }
  141. #endregion
  142. #region Actions
  143. private void ReloadActions(string sectionName, DataModel model)
  144. {
  145. SetupActions.Clear();
  146. HostControl.ClearActions();
  147. HostControl.ClearReports();
  148. if (CurrentPanel != null)
  149. CurrentPanel.CreateToolbarButtons(this);
  150. CreateModules(sectionName, model);
  151. CreateReports(sectionName, model);
  152. }
  153. #endregion
  154. #region Custom Modules
  155. private void CreateModules(string section, DataModel model)
  156. {
  157. if (ClientFactory.IsSupported<CustomModule>())
  158. {
  159. foreach (var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(section, model))
  160. {
  161. HostControl.CreatePanelAction(new PanelAction
  162. {
  163. Caption = module.Name ?? "",
  164. Image = image,
  165. OnExecute = (action) =>
  166. {
  167. ClickModule(action, module);
  168. }
  169. });
  170. }
  171. }
  172. }
  173. private void ClickModule(PanelAction action, CustomModule code)
  174. {
  175. if (CurrentPanel != null)
  176. {
  177. if (!string.IsNullOrWhiteSpace(code.Script))
  178. try
  179. {
  180. Selection selection;
  181. if (code.SelectedRecords && code.AllRecords)
  182. selection = RecordSelectionDialog.Execute();
  183. else if (code.SelectedRecords)
  184. selection = Selection.Selected;
  185. else if (code.AllRecords)
  186. selection = Selection.All;
  187. else
  188. selection = Selection.None;
  189. var result = ScriptDocument.RunCustomModule(CurrentPanel.DataModel(selection), CurrentPanel.Selected(), code.Script);
  190. if (result)
  191. CurrentPanel.Refresh();
  192. }
  193. catch (CompileException c)
  194. {
  195. MessageWindow.ShowError(c.Message, c, shouldLog: false);
  196. }
  197. catch (Exception err)
  198. {
  199. MessageWindow.ShowError($"Unable to load {action.Caption}", err);
  200. }
  201. else
  202. MessageWindow.ShowMessage("Unable to load " + action.Caption, "Error", image: MessageWindow.WarningImage);
  203. }
  204. }
  205. private void ManageModules(PanelAction action)
  206. {
  207. if (CurrentPanel != null)
  208. {
  209. var section = CurrentPanel.SectionName;
  210. var dataModel = CurrentPanel.DataModel(Selection.Selected);
  211. var manager = new CustomModuleManager()
  212. {
  213. Section = section,
  214. DataModel = dataModel
  215. };
  216. manager.ShowDialog();
  217. ReloadActions(section, dataModel);
  218. }
  219. }
  220. #endregion
  221. #region Reports
  222. private IEnumerable<ReportExportDefinition> AddTemplateDefinitions()
  223. {
  224. if (CurrentPanel is null)
  225. return new List<ReportExportDefinition>() { new ReportExportDefinition("Email Report", PRSDesktop.Resources.email, ReportExportType.PDF,
  226. PRSEmailUtils.DoEmailReport)};
  227. else
  228. return PRSEmailUtils.CreateTemplateDefinitions(CurrentPanel.DataModel(Selection.None));
  229. }
  230. public static PanelAction CreateReportAction(ReportTemplate template, Func<Selection, DataModel> getDataModel)
  231. {
  232. var action = new PanelAction
  233. {
  234. Caption = template.Name,
  235. Image = PRSDesktop.Resources.printer,
  236. OnExecute = (action) =>
  237. {
  238. PrintReport(template.ID, getDataModel);
  239. }
  240. };
  241. if (Security.IsAllowed<CanDesignReports>())
  242. {
  243. var menu = new ContextMenu();
  244. menu.AddItem("Design Report", PRSDesktop.Resources.pencil, () => DesignReport(template.ID, getDataModel));
  245. action.Menu = menu;
  246. }
  247. return action;
  248. }
  249. private void CreateReports(string section, DataModel model)
  250. {
  251. if (CurrentPanel is null) return;
  252. var client = new Client<ReportTemplate>();
  253. var templates = ReportUtils.LoadReports(section, model, Columns.None<ReportTemplate>().Add(x => x.ID, x => x.Name));
  254. foreach (var template in templates)
  255. {
  256. HostControl.CreateReport(CreateReportAction(template, CurrentPanel.DataModel));
  257. }
  258. }
  259. private static void DesignReport(Guid templateID, Func<Selection, DataModel> getDataModel)
  260. {
  261. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(templateID)).FirstOrDefault();
  262. if (template is null)
  263. {
  264. Logger.Send(LogType.Error, "", $"No Report Template with ID '{templateID}'");
  265. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  266. return;
  267. }
  268. ReportUtils.DesignReport(template, getDataModel(Selection.None));
  269. }
  270. private static void PrintReport(Guid id, Func<Selection, DataModel> getDataModel)
  271. {
  272. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(id)).FirstOrDefault();
  273. if (template == null)
  274. {
  275. Logger.Send(LogType.Error, "", $"No Report Template with ID '{id}'");
  276. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  277. return;
  278. }
  279. var selection = Selection.None;
  280. if (template.SelectedRecords && template.AllRecords)
  281. selection = RecordSelectionDialog.Execute();
  282. else if (template.SelectedRecords)
  283. selection = Selection.Selected;
  284. else if (template.AllRecords)
  285. selection = Selection.All;
  286. else
  287. MessageWindow.ShowMessage(
  288. "Report must have either [Selected Records] or [All Records] checked to display!",
  289. "Error",
  290. image: MessageWindow.WarningImage);
  291. if (selection != Selection.None)
  292. ReportUtils.PreviewReport(template, getDataModel(selection), false, Security.IsAllowed<CanDesignReports>());
  293. }
  294. private void ManageReports(PanelAction action)
  295. {
  296. if (CurrentPanel is null)
  297. return;
  298. var section = CurrentPanel.SectionName;
  299. var model = CurrentPanel.DataModel(Selection.None);
  300. if (model == null)
  301. {
  302. MessageWindow.ShowMessage("No DataModel for " + CurrentPanel.SectionName, "No DataModel");
  303. return;
  304. }
  305. var form = new ReportManager { DataModel = model, Section = section, Populate = true };
  306. form.ShowDialog();
  307. ReloadActions(section, model);
  308. }
  309. private void ManageEmailTemplates(PanelAction action)
  310. {
  311. if (CurrentPanel is null)
  312. return;
  313. var section = CurrentPanel.SectionName;
  314. var model = CurrentPanel.DataModel(Selection.None);
  315. if (model == null)
  316. {
  317. MessageWindow.ShowMessage("No DataModel for " + section, "No DataModel");
  318. return;
  319. }
  320. var window = new EmailTemplateManagerWindow(model);
  321. window.ShowDialog();
  322. }
  323. #endregion
  324. #region Public Interface
  325. public void InitialiseSetupMenu(ContextMenu menu)
  326. {
  327. var items = new List<IPanelActionItem>();
  328. items.AddRange(SetupActions);
  329. items.Add(new PanelActionSeparator());
  330. if (Security.IsAllowed<CanCustomiseModules>())
  331. {
  332. items.Add(new PanelAction("Custom Modules", PRSDesktop.Resources.script, ManageModules));
  333. }
  334. if (Security.IsAllowed<CanDesignReports>())
  335. {
  336. items.Add(new PanelAction("Reports", PRSDesktop.Resources.printer, ManageReports));
  337. }
  338. if (Security.IsAllowed<CanDesignReports>())
  339. {
  340. items.Add(new PanelAction("Email Templates", PRSDesktop.Resources.email, ManageEmailTemplates));
  341. }
  342. for (var i = 0; i < items.Count; ++i)
  343. {
  344. var item = items[i];
  345. if (item is PanelAction setupAction)
  346. {
  347. menu.AddItem(setupAction.Caption, setupAction.Image, setupAction, setupAction.OnExecute);
  348. }
  349. else if (item is PanelActionSeparator && i > 0 && i < items.Count - 1)
  350. {
  351. var last = items[i - 1];
  352. if (last is not PanelActionSeparator)
  353. menu.AddSeparator();
  354. }
  355. }
  356. if (CurrentPanel?.GetType().HasInterface(typeof(IPropertiesPanel<>)) == true && Security.IsAllowed<CanConfigurePanels>())
  357. {
  358. var securityInterface = CurrentPanel?.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<,>));
  359. var canConfigure = false;
  360. if (securityInterface is not null)
  361. {
  362. var token = securityInterface.GenericTypeArguments[1];
  363. canConfigure = Security.IsAllowed(token);
  364. }
  365. else
  366. {
  367. canConfigure = Security.IsAllowed<CanConfigurePanels>();
  368. }
  369. if (canConfigure)
  370. {
  371. menu.AddItem("Configure Panel", PRSDesktop.Resources.edit, ConfigurePanel);
  372. }
  373. }
  374. if (menu.Items.Count == 0)
  375. {
  376. menu.AddItem("No Items", null, null, false);
  377. }
  378. }
  379. public IBasePanel LoadPanel(Type T, string moduleName)
  380. {
  381. return (LoadPanelMethod.MakeGenericMethod(T).Invoke(this, new object[] { moduleName })
  382. as IBasePanel)!;
  383. }
  384. private static readonly MethodInfo LoadPanelMethod = typeof(PanelHost)
  385. .GetMethods().First(x => x.Name == nameof(LoadPanel) && x.IsGenericMethod);
  386. public T LoadPanel<T>(string moduleName) where T : class, IBasePanel, new()
  387. {
  388. var panel = new T();
  389. CurrentPanel = panel;
  390. ReportUtils.ExportDefinitions.Clear();
  391. ReportUtils.ExportDefinitions.AddRange(AddTemplateDefinitions());
  392. InitializePanelProperties(panel);
  393. CurrentModuleName = moduleName;
  394. TrackedTicks = DateTime.Now;
  395. CurrentPanel.IsReady = false;
  396. CurrentPanel.Setup();
  397. CurrentPanel.IsReady = true;
  398. CurrentPanel.OnUpdateDataModel += ReloadActions;
  399. var model = CurrentPanel.DataModel(Selection.None);
  400. var section = CurrentPanel.SectionName;
  401. ReloadActions(section, model);
  402. return panel;
  403. }
  404. public void Refresh()
  405. {
  406. CurrentPanel?.Refresh();
  407. }
  408. private void Heartbeat(TimeSpan time, bool closing)
  409. {
  410. if (!closing && time.TotalMinutes < 5)
  411. return;
  412. TrackedTicks = DateTime.Now;
  413. if (CurrentPanel is not null)
  414. {
  415. //Logger.Send(LogType.Information, "", string.Format("Heartbeat: {0}", CurrentPanel_Label));
  416. if (ClientFactory.IsSupported<ModuleTracking>())
  417. {
  418. var keys = TrackedKeys;
  419. TrackedKeys = 0;
  420. var clicks = TrackedClicks;
  421. TrackedClicks = 0;
  422. var tracking = new ModuleTracking
  423. {
  424. Date = DateTime.Today,
  425. Module = CurrentModuleName,
  426. Clicks = clicks,
  427. Keys = keys,
  428. ActiveTime = clicks + keys > 0 ? time : new TimeSpan(),
  429. IdleTime = clicks + keys == 0 ? time : new TimeSpan()
  430. };
  431. tracking.User.ID = ClientFactory.UserGuid;
  432. new Client<ModuleTracking>().Save(tracking, "", (mt, ex) => { });
  433. }
  434. CurrentPanel.Heartbeat(time);
  435. }
  436. }
  437. public void Heartbeat()
  438. {
  439. Heartbeat(DateTime.Now - TrackedTicks, false);
  440. }
  441. public void UnloadPanel(CancelEventArgs? cancel)
  442. {
  443. if (CurrentPanel != null)
  444. {
  445. Heartbeat(DateTime.Now - TrackedTicks, true);
  446. try
  447. {
  448. CurrentPanel.Shutdown(cancel);
  449. if (cancel?.Cancel == true)
  450. {
  451. return;
  452. }
  453. }
  454. catch (Exception e)
  455. {
  456. Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Error in UnloadPanel(): {0}\n{1}", e.Message, e.StackTrace));
  457. }
  458. TrackedTicks = DateTime.MinValue;
  459. CurrentModuleName = "";
  460. TrackedClicks = 0;
  461. TrackedKeys = 0;
  462. }
  463. }
  464. #endregion
  465. }