using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Threading; using System.Windows.Forms; using log4net; using NHibernate; using Common; using Common.Forms; using SharedDatabase; using SharedDatabase.Entities; using SharedDatabase.Forms; using WorkflowConfigurator.Resources; namespace WorkflowConfigurator.UserControls { public partial class EditProcessCtrl : UserControl, IMyUI { static readonly ILog log = LogManager.GetLogger(typeof(EditProcessCtrl)); /// /// Group membership to unlock this screen /// public static GID[] UnlockAccessLevel = new GID[] { GID.TraceabilityManagement }; public static GID[] ChangeReleaseState = new GID[] { GID.TraceabilityManagement }; public OverviewOfProcessesCtrl OverviewCtrl; /// Parent control reference /// /// The following section contains 'state' /// public Process Process; /// Process.ReleaseStatus bool suppressNotifications; bool limitedUnlock; bool completeUnlock; bool unsavedChanges; IList allProcesses; /// Auxiliary, list of all processes used in checks, updated in UpdateProcess() public IEnumerable SelectedParts { get { return partsCtrl.SelectedParts; } } public IList PartsToBeDeleted; public IList WorkstepsToBeDeleted; public EditProcessCtrl() { InitializeComponent(); unsavedChanges = false; suppressNotifications = false; saveButton.Enabled = false; PartsToBeDeleted = new List(); WorkstepsToBeDeleted = new List(); partsCtrl.EditProcessCtrl = this; workstepsCtrl.EditProcessCtrl = this; Localize(); UpdateLockState(false, false); /// Locked } void Localize() { /// Labels string colon = ":"; processNameLabel.Text = Strings.Workflow + colon; processDescriptionLabel.Text = Strings.Description + colon; createdByLabel.Text = Strings.Created_by + colon; approvedByLabel.Text = Strings.Approved_by + colon; inPreparationButton.Text = Strings.In_preparation; testingButton.Text = Strings.Testing; productionButton.Text = Strings.Production; deactivatedButton.Text = Strings.Deactivated; showRecordsButton.Text = Strings.Show_records; /// Buttons saveButton.Text = Strings.Save; unlockButton.Text = Strings.Unlock; notesButton.Text = Strings.Notes; } /// /// Called from Overview... /// public void UpdateProcess(Process process, Reason reason, IList allProcesses) { if (allProcesses != null) this.allProcesses = allProcesses; /// Update content this.Process = process; partsCtrl.UpdateProcess(process, reason, allProcesses); workstepsCtrl.UpdateProcess(process, reason, allProcesses); Data2UI(); /// Update UI state StateChange(reason); /// Reset 'unsavedChanges', a new or copied process always contains unsaved changes unsavedChanges = (reason == Reason.NewProcess) || (reason == Reason.CopyProcess) || ((process != null) && (process.Id == 0)); saveButton.Enabled = unsavedChanges; PartsToBeDeleted.Clear(); WorkstepsToBeDeleted.Clear(); } /// /// Called from Overview... /// Finish editing the current process. /// /// true : proces was modified, false : process was not modified public EditFinishedInfo FinishEditing() { string message; if (!unsavedChanges) { /// No changes done => just return return EditFinishedInfo.NoChanges; } else if (!VerifyUIData(out message)) { /// Data in UI are not correct => Yes/No message box if (new Forms.EditOrAbandonMsgBox(string.Format(Strings.Invalid_data_0, string.Empty), message).ShowDialog() == DialogResult.Retry) { /// Yes = continue editing, cancel callers action return EditFinishedInfo.Cancel; } else { /// No = abandon changes, continue callers action return EditFinishedInfo.NoChanges; } } else { /// Data in UI are correct => ask whether to save them if (MessageBox.Show(Strings.Unsaved_changes_Do_you_want_to_save_them_nowQM, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { /// Yes = save changes return SaveChanges() ? EditFinishedInfo.DataChanged : EditFinishedInfo.NoChanges; } else { /// No = abandon changes (just return) return EditFinishedInfo.NoChanges; } } } /// /// Retrieve data from UI and save them to database /// /// true when successful bool SaveChanges() { /// Retrieve data from UI UI2Data(); partsCtrl.UI2Data(); workstepsCtrl.UI2Data(); if (SaveDataToDB(SharedDatabase.TracingDB.Session)) { /// Saving data was successful SharedDatabase.TracingDB.Session.Refresh(Process); return true; } else { /// Saving data failed MessageBox.Show(Strings.Saving_changes_failed); return false; } } public void Data2UI() { suppressNotifications = true; if (Process != null) { processNameTextBox.Text = Process.Name; processDescriptionTextBox.Text = Process.Description; } else { processNameTextBox.Text = string.Empty; processDescriptionTextBox.Text = string.Empty; } CreatedByOn2UI(Process); UpdateProcessStateButtonsAndPicture(Process); partsCtrl.Data2UI(); workstepsCtrl.Data2UI(); suppressNotifications = false; } void CreatedByOn2UI(Process process) { createdByTextBox.Text = (process == null) ? string.Empty : process.CreatedBy; createdOnTextBox.Text = (process == null) ? string.Empty : process.TimeStamp.ToString("dd.MM.yyyy HH:mm"); approvedByTextBox.Text = (process == null) ? string.Empty : process.ApprovedBy; approvedOnTextBox.Text = (process == null) ? string.Empty : process.TimeStamp2.ToString("dd.MM.yyyy HH:mm"); } /// /// Returns true when (modified) UI data are OK /// /// true when UI data are valid public bool VerifyUIData(out string message) { bool uiDataValid = true; message = string.Empty; if (Process == null) return uiDataValid; if (allProcesses != null) { foreach (var p in allProcesses) { if ((processNameTextBox.Text == p.Name) && (Process.Id != p.Id)) { uiDataValid = false; message += string.Format(Strings.Workflow_with_duplicate_name_exists) + Environment.NewLine; break; } } } string msg; if (!partsCtrl.VerifyUIData(out msg)) { uiDataValid = false; message += msg; } /// Parts if (!workstepsCtrl.VerifyUIData(out msg)) { uiDataValid = false; message += msg; } /// Worksteps return uiDataValid; } public void UI2Data() { if (this.Process != null) { Process.Name = processNameTextBox.Text; Process.Description = processDescriptionTextBox.Text; //createdByTextBox.Text = this.Process.CreatedBy; //createdOnTextBox.Text = this.Process.TimeStamp.ToShortDateString(); //approvedByTextBox.Text = this.Process.ApprovedBy; //approvedOnTextBox.Text = this.Process.TimeStamp2.ToShortDateString(); //stateLabel.Text = this.Process.ReleaseStatus.ToString(); } partsCtrl.UI2Data(); workstepsCtrl.UI2Data(); } public void Settings2UI() { var ls = Program.LocalSettings; splitContainerV2.SplitterDistance = Math.Max(splitContainerV2.Panel1MinSize, Math.Min(splitContainerV2.Width - splitContainerV2.Panel2MinSize, (ls.SplDst2 > 0) ? Convert.ToInt32(ls.SplDst2 * Width) : 50)); partsCtrl.Settings2UI(); workstepsCtrl.Settings2UI(); } public void UI2Settings() { Program.LocalSettings.SplDst2 = (float)splitContainerV2.SplitterDistance / (float)Width; partsCtrl.UI2Settings(); workstepsCtrl.UI2Settings(); } /// /// Set 'unsavedChanges' and enable 'Save' button /// public void SetUnsavedFlag() { unsavedChanges = true; saveButton.Enabled = true; } /// /// Reset 'unsavedChanges' and disable 'Save' button /// public void ResetUnsavedFlag() { unsavedChanges = false; partsCtrl.ResetUnsavedFlag(); workstepsCtrl.ResetUnsavedFlag(); saveButton.Enabled = false; } /// /// Enable/disable/hide UI elements based on lock/unlock/complete unlock state /// /// true = unlock for released processes /// true = unlock for processes in preparation (complete unlock) public void UpdateLockState(bool limitedUnlock, bool completeUnlock) { this.limitedUnlock = limitedUnlock; this.completeUnlock = completeUnlock; partsCtrl.UpdateLockState(limitedUnlock, completeUnlock); /// Parts workstepsCtrl.UpdateLockState(limitedUnlock, completeUnlock); /// Worksteps unlockButton.Enabled = !(completeUnlock || limitedUnlock); processNameTextBox.Enabled = (limitedUnlock || completeUnlock); processDescriptionTextBox.Enabled = (limitedUnlock || completeUnlock); createdByTextBox.Enabled = false; createdOnTextBox.Enabled = false; approvedByTextBox.Enabled = false; approvedOnTextBox.Enabled = false; } void UpdateProcessStateButtonsAndPicture(Process process) { inPreparationButton.Text = Strings.In_preparation; testingButton.Text = Strings.Testing; productionButton.Text = Strings.Production; deactivatedButton.Text = Strings.Deactivated; showRecordsButton.Visible = false; if (process == null) { inPreparationButton.Enabled = false; testingButton.Enabled = false; productionButton.Enabled = false; deactivatedButton.Enabled = false; statePictureBox.Image = Image.FromFile(string.Format("{0}\\Pictures\\{1}.bmp", Program.ExeDirectory, "0"), true); return; } switch (process.ReleaseStatus) { default: case ReleaseStatus.In_preparation: inPreparationButton.Enabled = false; testingButton.Enabled = (limitedUnlock || completeUnlock); testingButton.Text = Strings.Test; productionButton.Enabled = false; deactivatedButton.Enabled = false; statePictureBox.Image = Image.FromFile(string.Format("{0}\\Pictures\\{1}.bmp", Program.ExeDirectory, "1"), true); break; case ReleaseStatus.ToBeApproved: inPreparationButton.Enabled = (limitedUnlock || completeUnlock); inPreparationButton.Text = Strings.Modify; testingButton.Enabled = false; productionButton.Enabled = (limitedUnlock || completeUnlock); productionButton.Text = Strings.Release_to_production; deactivatedButton.Enabled = false; statePictureBox.Image = Image.FromFile(string.Format("{0}\\Pictures\\{1}.bmp", Program.ExeDirectory, "2"), true); showRecordsButton.Visible = true; break; case ReleaseStatus.ReleasedActive: case ReleaseStatus.Released: inPreparationButton.Enabled = false; testingButton.Enabled = false; productionButton.Enabled = false; deactivatedButton.Enabled = (limitedUnlock || completeUnlock); deactivatedButton.Text = Strings.Deactivate; statePictureBox.Image = Image.FromFile(string.Format("{0}\\Pictures\\{1}.bmp", Program.ExeDirectory, "3"), true); showRecordsButton.Visible = true; break; case ReleaseStatus.Deactivated: inPreparationButton.Enabled = false; testingButton.Enabled = false; productionButton.Enabled = (limitedUnlock || completeUnlock); deactivatedButton.Enabled = true; deactivatedButton.Text = Strings.Activate_again; statePictureBox.Image = Image.FromFile(string.Format("{0}\\Pictures\\{1}.bmp", Program.ExeDirectory, "4"), true); showRecordsButton.Visible = true; break; } } /// /// Adds a part to the currently selected workstep. /// Display amesssage box when addin a part fails. /// /// Part to be added /// true when a part was successfully added public bool AddPartsToCurrentWorkstep(Part part) { return workstepsCtrl.AddPartsToCurrentWorkstep(part); } public Part RemovePartFromCurrentWorkstep() { return workstepsCtrl.RemovePartFromCurrentWorkstep(); } private void notesButton_Click(object sender, EventArgs e) { //StringBuilder sb = new StringBuilder(); //sb.AppendLine(string.Format("AnyChange = {0}", workstepsTabControl.TabOrderChanged)); //foreach (TabPage tp in workstepsTabControl.TabPages) //{ // sb.AppendLine(tp.Text); //} //MessageBox.Show(sb.ToString()); //if ((Process == null) || string.IsNullOrEmpty(Process.ReleaseNotes)) //{ // MessageBox.Show(Strings.No_release_notes); //} //else //{ // MessageBox.Show(Process.ReleaseNotes); //} } /// /// Save a process to the DB /// /// DB session /// true when successful public bool SaveDataToDB(ISession session) { if (Process == null) return true; string message; if (!VerifyUIData(out message)) { MessageBox.Show(string.Format(Strings.Invalid_data_0, message), Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return false; } UI2Data(); partsCtrl.UI2Data(); workstepsCtrl.UI2Data(); ITransaction transaction = session.BeginTransaction(); try { session.SaveOrUpdate(Process); foreach (var part in PartsToBeDeleted) session.Delete(part); foreach (var ws in WorkstepsToBeDeleted) { ws.Workflow = null; ws.ReferencePart = null; ws.Parts.Clear(); session.Delete(ws); } transaction.Commit(); session.Flush(); PartsToBeDeleted.Clear(); WorkstepsToBeDeleted.Clear(); ResetUnsavedFlag(); return true; } catch (Exception exc) { transaction.Rollback(); log.ErrorFormat("Saving process '{0}' to DB failed: {1}", Process.Name, exc.Message); MessageBox.Show(string.Format("{0}{1}{2}", Strings.Saving_changes_failed, Environment.NewLine, exc.Message), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return false; } } private void processNameTextBox_TextChanged(object sender, EventArgs e) { if (suppressNotifications) return; unsavedChanges = true; saveButton.Enabled = true; } private void processDescriptionTextBox_TextChanged(object sender, EventArgs e) { if (suppressNotifications) return; unsavedChanges = true; saveButton.Enabled = true; } private void saveButton_Click(object sender, EventArgs e) { if (SaveDataToDB(SharedDatabase.TracingDB.Session)) { ResetUnsavedFlag(); if (OverviewCtrl != null) OverviewCtrl.RedrawLeftPane(RedrawType.FromScratch, Process); } } private void unlockButton_Click(object sender, EventArgs e) { StateChange(Reason.UnlockBtn); } private void inPreparationButton_Click(object sender, EventArgs e) { StateChange(Reason.PrepareBtn); } private void testingButton_Click(object sender, EventArgs e) { StateChange(Reason.TestBtn); } private void productionButton_Click(object sender, EventArgs e) { StateChange(Reason.ReleaseBtn); } private void deactivatedButton_Click(object sender, EventArgs e) { StateChange(Reason.DeactivateBtn); } /// /// Change 'ReleaseStatus' of the procedure and update UI / enable/disable UI controls accordingly /// when changing the state of the procedure, unlocking UI or opening/creating/copying a procedure. /// /// Reason of the state change void StateChange(Reason reason) { if (Process == null) return; /// No process bool unlocked = (limitedUnlock || completeUnlock); if (reason == Reason.OpenProcess) { /// Existing process was opened --> locked UpdateLockState(false, false); UpdateProcessStateButtonsAndPicture(Process); return; } else if (reason == Reason.NewProcess) { /// A new process was created --> completely unlocked, optionally import a parts list UpdateLockState(true, true); UpdateProcessStateButtonsAndPicture(Process); SetUnsavedFlag(); if (MessageBox.Show(Strings.Do_you_want_to_import_parts_from_a_list, Strings.Import, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { ImportParts(); } return; } else if (reason == Reason.CopyProcess) { /// A new process was created as a copy of an existing process --> completely unlocked UpdateLockState(true, true); UpdateProcessStateButtonsAndPicture(Process); SetUnsavedFlag(); return; } else if (reason == Reason.UnlockBtn) { /// Unlock button pressed if (unlocked) return; /// Already unlocked if (CurrentUser.IsMemberOf(UnlockAccessLevel) || new SharedDatabase.Forms.LoginDlg(UnlockAccessLevel, null).ShowDialog() == DialogResult.OK) { MainWnd.PrintTitle(false); UpdateLockState(true, (Process.ReleaseStatus == ReleaseStatus.In_preparation)); UpdateProcessStateButtonsAndPicture(Process); return; } else { return; /// Access not granted --> locked } } /// /// Change the state of the current process /// FinishEditing(); /// ReleaseStatus oriState = Process.ReleaseStatus; /// switch (reason) { case Reason.PrepareBtn: if (!GetAccessToStateChange(false)) return; /// Login if unsufficient access right UpdateLockState(unlocked, unlocked); Process.ReleaseStatus = ReleaseStatus.In_preparation; break; case Reason.TestBtn: if (!GetAccessToStateChange(false)) return; UpdateLockState(unlocked, false); Process.ReleaseStatus = ReleaseStatus.ToBeApproved; break; case Reason.ReleaseBtn: if (!GetAccessToStateChange(true)) return; /// Obligatory login when releasing a process to production UpdateLockState(false, false); Process.ReleaseStatus = ReleaseStatus.Released; Process.ApprovedBy = CurrentUser.UserName(); Process.TimeStamp2 = DateTime.Now; break; case Reason.DeactivateBtn: if (!GetAccessToStateChange(false)) return; UpdateLockState(false, false); if (Process.ReleaseStatus == ReleaseStatus.ReleasedActive || Process.ReleaseStatus == ReleaseStatus.Released) { Process.ReleaseStatus = ReleaseStatus.Deactivated; } else { Process.ReleaseStatus = ReleaseStatus.Released; } break; default: return; /// Otherwise do nothing } /// /// Save the process after changing the state, revert state to original if saving fails /// if (SaveDataToDB(SharedDatabase.TracingDB.Session)) { /// Saving successful UpdateProcessStateButtonsAndPicture(Process); CreatedByOn2UI(Process); ResetUnsavedFlag(); if (OverviewCtrl != null) OverviewCtrl.RedrawLeftPane(RedrawType.FromScratch, Process); } else { /// Saving failed Process.ReleaseStatus = oriState; } } public void ImportParts() { partsCtrl.ImportParts(); } public void ImportPartsUsingTemplate() { IList patterns = new List(); foreach (var pt in Process.Parts) { if (pt.Name == "0") { patterns.Add(pt.Description.Split(new char[]{'|'})); } else { patterns.Add(new string[] { string.Empty }); } } OpenFileDialog dlg = new OpenFileDialog(); if (dlg.ShowDialog() == DialogResult.OK) { try { using (TextReader reader = new StreamReader(dlg.FileName)) { bool lineOK; bool lineAssigned; do { string line = reader.ReadLine(); lineOK = false; lineAssigned = false; if (line != null) { string[] items = line.Split(new char[] { ';' }); if (items.Length >= 2) { lineOK = true; for (int i = 0; i < Process.Parts.Count; i++) { if (Process.Parts[i].Name == "0") { foreach (var pattern in patterns[i]) { if (items[1].Contains(pattern)) { Process.Parts[i].Name = items[0]; Process.Parts[i].Description = items[1]; lineAssigned = true; break; } } } if (lineAssigned) break; } } } } while (lineOK); } Data2UI(); } catch (Exception) { MessageBox.Show(Strings.Failed_to_import_a_list_of_parts, Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } } bool GetAccessToStateChange(bool forceLogin) { GID[] rqrdMmbrshp = ChangeReleaseState; if (forceLogin) { return new LoginDlg(rqrdMmbrshp, null).ShowDialog() == DialogResult.OK; } else { return CurrentUser.IsMemberOf(rqrdMmbrshp) || new LoginDlg(rqrdMmbrshp, null).ShowDialog() == DialogResult.OK; } } private void showRecordsButton_Click(object sender, EventArgs e) { if (SharedDatabase.TracingDB.Session != null) { IList refRecords = SharedDatabase.TracingDB.Session.QueryOver().Where(x => (x.Workflow == Process.Name)).List(); new Forms.ShowRecordsForm(refRecords).ShowDialog(); } } } }