PanelHost.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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. CreateModules(sectionName, model);
  149. if (CurrentPanel != null)
  150. {
  151. CurrentPanel.CreateToolbarButtons(this);
  152. }
  153. CreateReports(sectionName, model);
  154. }
  155. #endregion
  156. #region Custom Modules
  157. private void CreateModules(string section, DataModel model)
  158. {
  159. if (ClientFactory.IsSupported<CustomModule>())
  160. {
  161. foreach (var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(section, model))
  162. {
  163. HostControl.CreatePanelAction(new PanelAction
  164. {
  165. Caption = module.Name ?? "",
  166. Image = image,
  167. OnExecute = (action) =>
  168. {
  169. ClickModule(action, module);
  170. }
  171. });
  172. }
  173. }
  174. }
  175. private void ClickModule(PanelAction action, CustomModule code)
  176. {
  177. if (CurrentPanel != null)
  178. {
  179. if (!string.IsNullOrWhiteSpace(code.Script))
  180. try
  181. {
  182. Selection selection;
  183. if (code.SelectedRecords && code.AllRecords)
  184. selection = RecordSelectionDialog.Execute();
  185. else if (code.SelectedRecords)
  186. selection = Selection.Selected;
  187. else if (code.AllRecords)
  188. selection = Selection.All;
  189. else
  190. selection = Selection.None;
  191. var result = ScriptDocument.RunCustomModule(CurrentPanel.DataModel(selection), CurrentPanel.Selected(), code.Script);
  192. if (result)
  193. CurrentPanel.Refresh();
  194. }
  195. catch (CompileException c)
  196. {
  197. MessageWindow.ShowError(c.Message, c, shouldLog: false);
  198. }
  199. catch (Exception err)
  200. {
  201. MessageWindow.ShowError($"Unable to load {action.Caption}", err);
  202. }
  203. else
  204. MessageWindow.ShowMessage("Unable to load " + action.Caption, "Error", image: MessageWindow.WarningImage);
  205. }
  206. }
  207. private void ManageModules(PanelAction action)
  208. {
  209. if (CurrentPanel != null)
  210. {
  211. var section = CurrentPanel.SectionName;
  212. var dataModel = CurrentPanel.DataModel(Selection.Selected);
  213. var manager = new CustomModuleManager()
  214. {
  215. Section = section,
  216. DataModel = dataModel
  217. };
  218. manager.ShowDialog();
  219. ReloadActions(section, dataModel);
  220. }
  221. }
  222. #endregion
  223. #region Reports
  224. private IEnumerable<ReportExportDefinition> AddTemplateDefinitions()
  225. {
  226. if (CurrentPanel is null)
  227. return new List<ReportExportDefinition>() { new ReportExportDefinition("Email Report", PRSDesktop.Resources.email, ReportExportType.PDF,
  228. PRSEmailUtils.DoEmailReport)};
  229. else
  230. return PRSEmailUtils.CreateTemplateDefinitions(CurrentPanel.DataModel(Selection.None));
  231. }
  232. public static PanelAction CreateReportAction(ReportTemplate template, Func<Selection, DataModel> getDataModel)
  233. {
  234. var action = new PanelAction
  235. {
  236. Caption = template.Name,
  237. Image = PRSDesktop.Resources.printer,
  238. OnExecute = (action) =>
  239. {
  240. PrintReport(template.ID, getDataModel);
  241. }
  242. };
  243. if (Security.IsAllowed<CanDesignReports>())
  244. {
  245. var menu = new ContextMenu();
  246. menu.AddItem("Design Report", PRSDesktop.Resources.pencil, () => DesignReport(template.ID, getDataModel));
  247. action.Menu = menu;
  248. }
  249. return action;
  250. }
  251. private void CreateReports(string section, DataModel model)
  252. {
  253. if (CurrentPanel is null) return;
  254. var client = new Client<ReportTemplate>();
  255. var templates = ReportUtils.LoadReports(section, model, new Columns<ReportTemplate>(x => x.ID, x => x.Name));
  256. foreach (var template in templates)
  257. {
  258. HostControl.CreateReport(CreateReportAction(template, CurrentPanel.DataModel));
  259. }
  260. }
  261. private static void DesignReport(Guid templateID, Func<Selection, DataModel> getDataModel)
  262. {
  263. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(templateID)).FirstOrDefault();
  264. if (template is null)
  265. {
  266. Logger.Send(LogType.Error, "", $"No Report Template with ID '{templateID}'");
  267. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  268. return;
  269. }
  270. ReportUtils.DesignReport(template, getDataModel(Selection.None));
  271. }
  272. private static void PrintReport(Guid id, Func<Selection, DataModel> getDataModel)
  273. {
  274. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(id)).FirstOrDefault();
  275. if (template == null)
  276. {
  277. Logger.Send(LogType.Error, "", $"No Report Template with ID '{id}'");
  278. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  279. return;
  280. }
  281. var selection = Selection.None;
  282. if (template.SelectedRecords && template.AllRecords)
  283. selection = RecordSelectionDialog.Execute();
  284. else if (template.SelectedRecords)
  285. selection = Selection.Selected;
  286. else if (template.AllRecords)
  287. selection = Selection.All;
  288. else
  289. MessageWindow.ShowMessage(
  290. "Report must have either [Selected Records] or [All Records] checked to display!",
  291. "Error",
  292. image: MessageWindow.WarningImage);
  293. if (selection != Selection.None)
  294. ReportUtils.PreviewReport(template, getDataModel(selection), false, Security.IsAllowed<CanDesignReports>());
  295. }
  296. private void ManageReports(PanelAction action)
  297. {
  298. if (CurrentPanel is null)
  299. return;
  300. var section = CurrentPanel.SectionName;
  301. var model = CurrentPanel.DataModel(Selection.None);
  302. if (model == null)
  303. {
  304. MessageWindow.ShowMessage("No DataModel for " + CurrentPanel.SectionName, "No DataModel");
  305. return;
  306. }
  307. var form = new ReportManager { DataModel = model, Section = section, Populate = true };
  308. form.ShowDialog();
  309. ReloadActions(section, model);
  310. }
  311. private void ManageEmailTemplates(PanelAction action)
  312. {
  313. if (CurrentPanel is null)
  314. return;
  315. var section = CurrentPanel.SectionName;
  316. var model = CurrentPanel.DataModel(Selection.None);
  317. if (model == null)
  318. {
  319. MessageWindow.ShowMessage("No DataModel for " + section, "No DataModel");
  320. return;
  321. }
  322. var window = new EmailTemplateManagerWindow(model);
  323. window.ShowDialog();
  324. }
  325. #endregion
  326. #region Public Interface
  327. public void InitialiseSetupMenu(ContextMenu menu)
  328. {
  329. var items = new List<IPanelActionItem>();
  330. items.AddRange(SetupActions);
  331. items.Add(new PanelActionSeparator());
  332. if (Security.IsAllowed<CanCustomiseModules>())
  333. {
  334. items.Add(new PanelAction("Custom Modules", PRSDesktop.Resources.script, ManageModules));
  335. }
  336. if (Security.IsAllowed<CanDesignReports>())
  337. {
  338. items.Add(new PanelAction("Reports", PRSDesktop.Resources.printer, ManageReports));
  339. }
  340. if (Security.IsAllowed<CanDesignReports>())
  341. {
  342. items.Add(new PanelAction("Email Templates", PRSDesktop.Resources.email, ManageEmailTemplates));
  343. }
  344. for (var i = 0; i < items.Count; ++i)
  345. {
  346. var item = items[i];
  347. if (item is PanelAction setupAction)
  348. {
  349. menu.AddItem(setupAction.Caption, setupAction.Image, setupAction, setupAction.OnExecute);
  350. }
  351. else if (item is PanelActionSeparator && i > 0 && i < items.Count - 1)
  352. {
  353. var last = items[i - 1];
  354. if (last is not PanelActionSeparator)
  355. menu.AddSeparator();
  356. }
  357. }
  358. if (CurrentPanel?.GetType().HasInterface(typeof(IPropertiesPanel<>)) == true && Security.IsAllowed<CanConfigurePanels>())
  359. {
  360. var securityInterface = CurrentPanel?.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<,>));
  361. var canConfigure = false;
  362. if (securityInterface is not null)
  363. {
  364. var token = securityInterface.GenericTypeArguments[1];
  365. canConfigure = Security.IsAllowed(token);
  366. }
  367. else
  368. {
  369. canConfigure = Security.IsAllowed<CanConfigurePanels>();
  370. }
  371. if (canConfigure)
  372. {
  373. menu.AddItem("Configure Panel", PRSDesktop.Resources.edit, ConfigurePanel);
  374. }
  375. }
  376. if (menu.Items.Count == 0)
  377. {
  378. menu.AddItem("No Items", null, null, false);
  379. }
  380. }
  381. public IBasePanel LoadPanel(Type T, string moduleName)
  382. {
  383. return (LoadPanelMethod.MakeGenericMethod(T).Invoke(this, new object[] { moduleName })
  384. as IBasePanel)!;
  385. }
  386. private static readonly MethodInfo LoadPanelMethod = typeof(PanelHost)
  387. .GetMethods().First(x => x.Name == nameof(LoadPanel) && x.IsGenericMethod);
  388. public T LoadPanel<T>(string moduleName) where T : class, IBasePanel, new()
  389. {
  390. var panel = new T();
  391. CurrentPanel = panel;
  392. ReportUtils.ExportDefinitions.Clear();
  393. ReportUtils.ExportDefinitions.AddRange(AddTemplateDefinitions());
  394. InitializePanelProperties(panel);
  395. CurrentModuleName = moduleName;
  396. TrackedTicks = DateTime.Now;
  397. CurrentPanel.IsReady = false;
  398. CurrentPanel.Setup();
  399. CurrentPanel.IsReady = true;
  400. CurrentPanel.OnUpdateDataModel += ReloadActions;
  401. var model = CurrentPanel.DataModel(Selection.None);
  402. var section = CurrentPanel.SectionName;
  403. ReloadActions(section, model);
  404. return panel;
  405. }
  406. public void Refresh()
  407. {
  408. CurrentPanel?.Refresh();
  409. }
  410. private void Heartbeat(TimeSpan time, bool closing)
  411. {
  412. if (!closing && time.TotalMinutes < 5)
  413. return;
  414. TrackedTicks = DateTime.Now;
  415. if (CurrentPanel is not null)
  416. {
  417. //Logger.Send(LogType.Information, "", string.Format("Heartbeat: {0}", CurrentPanel_Label));
  418. if (ClientFactory.IsSupported<ModuleTracking>())
  419. {
  420. var keys = TrackedKeys;
  421. TrackedKeys = 0;
  422. var clicks = TrackedClicks;
  423. TrackedClicks = 0;
  424. var tracking = new ModuleTracking
  425. {
  426. Date = DateTime.Today,
  427. Module = CurrentModuleName,
  428. Clicks = clicks,
  429. Keys = keys,
  430. ActiveTime = clicks + keys > 0 ? time : new TimeSpan(),
  431. IdleTime = clicks + keys == 0 ? time : new TimeSpan()
  432. };
  433. tracking.User.ID = ClientFactory.UserGuid;
  434. new Client<ModuleTracking>().Save(tracking, "", (mt, ex) => { });
  435. }
  436. CurrentPanel.Heartbeat(time);
  437. }
  438. }
  439. public void Heartbeat()
  440. {
  441. Heartbeat(DateTime.Now - TrackedTicks, false);
  442. }
  443. public void UnloadPanel(CancelEventArgs? cancel)
  444. {
  445. if (CurrentPanel != null)
  446. {
  447. Heartbeat(DateTime.Now - TrackedTicks, true);
  448. try
  449. {
  450. CurrentPanel.Shutdown(cancel);
  451. if (cancel?.Cancel == true)
  452. {
  453. return;
  454. }
  455. }
  456. catch (Exception e)
  457. {
  458. Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Error in UnloadPanel(): {0}\n{1}", e.Message, e.StackTrace));
  459. }
  460. TrackedTicks = DateTime.MinValue;
  461. CurrentModuleName = "";
  462. TrackedClicks = 0;
  463. TrackedKeys = 0;
  464. }
  465. }
  466. #endregion
  467. }