dmdb/view/frmMv.cs

670 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using dezentrale.core;
using dezentrale.model;
namespace dezentrale.view
{
/**
* This form contains multiple tabs to handle the aspects of a
* MV, starting from invitation up to running the MV.
*/
public class frmMv : FormWithActionButtons
{
public Mv mv { get; set; }
private TabControl tabControl;
//List with all members and invited yes|no
private LvMvInvitations lvMvInvitations;
private TabPage tabInvitationSettings;
//DateTime
private DateTimePicker mvDate, mvTime;
//Place
private TextBox tbPlace;
//Agenda
private TextBox tbMvAgenda;
//Subject
private TextBox tbInviteHeadline;
private TextBox tbInviteBody;
//Option to invite selected|all members
private TabPage tabRunningMv;
private Label lblAttendedStats;
//Textbox to paste auth codes from the users
private TextBox tbAuthCodePaste;
private TextBox tbMvProtocol;
//textbox to hold an external MV protocol URL and/or
//[rich] textbox for MV protocol
//Button to finish MV
//MV attachments (e.g. finished PDF protocol)
private TabPage tabLog;
//! \brief Updates the stats labels for MV attend
private void UpdateAttendedDisplay()
{
int att = 0;
foreach(MvInvitedMember mvi in mv.Members)
{
if (mvi.AttendedMv) att++;
}
float percent;
if (mv.Members.Count > 0)
percent = ((float)att * (float)100 / (float)mv.Members.Count);
else
percent = 0;
lblAttendedStats.Text = $"{att} / {mv.Members.Count} ({Math.Floor(percent)} percent)";
if(percent <= 50)
{
lblAttendedStats.BackColor = System.Drawing.Color.Orange;
} else
{
lblAttendedStats.BackColor = System.Drawing.Color.LightGreen;
}
}
//! \brief UpdateGui() will refresh the GUI components for this
//! form according to the current MV state and visible
//! GUI tab. Having this centralized in one function is
//! useful for avoiding duplicate and hard to maintain code.<br/>
//! Subtasks:
//! - Enable/Disable GUI fields (like invitation text)
//! - Add the correct set of buttons to the toolbar
private void UpdateGui()
{
ClearButtons();
TabPage selectedTab = tabControl.SelectedTab;
lvMvInvitations.ClearMenuItems();
if(selectedTab == tabInvitationSettings)
{
bool enabled = (mv.Status == Mv.MvStatus.InPreparation || mv.Status == Mv.MvStatus.InvitationSent);
Button btn;
btn = AddButton("Invite selected", btnInviteSelected_Click); btn.Enabled = enabled;
btn = AddButton("Invite all", btnInviteAll_Click); btn.Enabled = enabled;
lvMvInvitations.AddMenuItem("Invite selected", btnInviteSelected_Click);
lvMvInvitations.AddMenuItem("Invite all", btnInviteAll_Click);
} else if(selectedTab == tabRunningMv)
{
Button btn;
btn = AddButton("Start MV", btnStartMv_Click); btn.Enabled = (mv.Status == Mv.MvStatus.InvitationSent);
btn = AddButton("End MV", btnFinishMv_Click); btn.Enabled = (mv.Status == Mv.MvStatus.Started);
lvMvInvitations.AddMenuItem("Check IN selected", btnCheckInSelected_Click);
lvMvInvitations.AddMenuItem("Check OUT selected", btnCheckOutSelected_Click);
lvMvInvitations.AddMenuItem("-");
MenuItem mi = lvMvInvitations.AddMenuItem("Set Membership to");
mi.MenuItems.Add(new MenuItem("Regulaer", (sender, e) => { }));
mi.MenuItems.Add(new MenuItem("Foerdermitglied", (sender, e) => { }));
UpdateAttendedDisplay();
} else if(selectedTab == tabLog)
{
}
AddButton("Close", btnClose_Click);
bool invitationPhase = (mv.Status == Mv.MvStatus.InPreparation)
|| (mv.Status == Mv.MvStatus.InvitationSent);
mvDate.Enabled = invitationPhase;
mvTime.Enabled = invitationPhase;
tbPlace.ReadOnly = !invitationPhase;
tbMvAgenda.ReadOnly = !invitationPhase;
tbInviteHeadline.ReadOnly= !invitationPhase;
tbInviteBody.ReadOnly = !invitationPhase;
tbAuthCodePaste.ReadOnly = mv.Status != Mv.MvStatus.Started;
tbMvProtocol.ReadOnly = invitationPhase;
switch (mv.Status)
{
case Mv.MvStatus.InPreparation:
break;
case Mv.MvStatus.InvitationSent:
case Mv.MvStatus.Started:
case Mv.MvStatus.Ended:
case Mv.MvStatus.Cancelled:
break;
}
}
private void DoInvitation(List<MvInvitedMember> mvMembers)
{
UpdateMvData();
if(mv.Status != Mv.MvStatus.InPreparation && mv.Status != Mv.MvStatus.InvitationSent)
{
MessageBox.Show($"MV needs to be in one of these states to invite members:\r\n{Mv.MvStatus.InPreparation}, {Mv.MvStatus.InvitationSent}");
return;
}
if (mvMembers == null || mvMembers.Count < 1)
{
MessageBox.Show("No members to invite!");
return;
}
//in order not to overwrite existing data, we're working on a non-deep copy here
List<MvInvitedMember> inv = new List<MvInvitedMember>(mvMembers);
//find non-active members
string subListString = "";
List<MvInvitedMember> subList = new List<MvInvitedMember>();
foreach(MvInvitedMember mvi in inv)
{
if(mvi.Member.Status != Member.eStatus.Active)
{
subListString += $"{mvi.Member.NumberString} - {mvi.Member.Nickname}";
subList.Add(mvi);
}
}
if(subList.Count > 0)
{
DialogResult res = MessageBox.Show($"There are {subList.Count} non-active members which will be skipped:\r\n{subListString}\r\n\r\nProceed?", "Skip non-active members?", MessageBoxButtons.OKCancel);
if (res == DialogResult.Cancel) return;
//I know this iteration is inefficient, but should do the trick for normal eV business.
foreach (MvInvitedMember mvi in subList) inv.Remove(mvi);
}
subListString = "";
subList.Clear();
foreach(MvInvitedMember mvi in inv)
{
if(mvi.Invited)
{
subListString += $"{mvi.Member.NumberString} - {mvi.Member.Nickname}";
subList.Add(mvi);
}
}
if (subList.Count > 0)
{
DialogResult res = MessageBox.Show($"There are {subList.Count} already-invited members:\r\n{subListString}\r\n\r\nInvite them again?", "Invite members again?", MessageBoxButtons.YesNoCancel);
if (res == DialogResult.Cancel) return;
//I know this iteration is inefficient, but should do the trick for normal eV business.
if (res == DialogResult.No)
foreach (MvInvitedMember mvi in subList) inv.Remove(mvi);
}
if(inv.Count < 1)
{
MessageBox.Show("There are no members to invite.");
return;
}
//Spawn invitation process window and run process
FormMail fm = (new FormMail()
{
To = "{EMailName} <{EMail}>",
Subject = mv.InviteHeadline,
Body = mv.InviteBody,
}).ReplaceReflect(mv);
SerialMailProcess<MvInvitedMember> invProcess = mv.GetInvitationMailProcess(inv);
frmProcessWithLog frmInvProcess = new frmProcessWithLog(invProcess, true);
frmInvProcess.ShowDialog();
if (invProcess.EntitiesSuccessful.Count > 0)
mv.Status = Mv.MvStatus.InvitationSent;
foreach (MvInvitedMember mvi in inv) lvMvInvitations.UpdateEntry(mvi);
UpdateGui();
}
private void btnInviteSelected_Click(object sender, EventArgs e)
{
DoInvitation(lvMvInvitations.GetSelectedItems());
}
private void btnInviteAll_Click(object sender, EventArgs e)
{
DoInvitation(mv.Members);
}
private void btnStartMv_Click(object sender, EventArgs e)
{
if (mv.Status != Mv.MvStatus.InvitationSent)
{
MessageBox.Show($"MV must be in state {Mv.MvStatus.InvitationSent}!");
return;
}
DateTime now = DateTime.Now;
DateTime evt = mv.EventDate;
if(now.Year != evt.Year || now.Month != evt.Month || now.Day != evt.Day || evt.CompareTo(now) > 0)
{
MessageBox.Show("Today must have the same Date as announced in the EventDate, and Time has to be after the announced time.");
return;
}
mv.Status = Mv.MvStatus.Started;
mv.StartDateTime = now;
//Todo: init protocol with contents from invitation mail
mv.Protocol = mv.AgendaNumberedString;
tbMvProtocol.Text = mv.Protocol;
MessageBox.Show("MV started.");
UpdateGui();
}
private void btnFinishMv_Click(object sender, EventArgs e)
{
if (mv.Status != Mv.MvStatus.Started)
{
MessageBox.Show($"MV must be in state {Mv.MvStatus.Started}!");
return;
}
MvFinishProcess finishProcess = new MvFinishProcess(mv);
frmProcessWithLog frmProcess = new frmProcessWithLog(finishProcess, false);
frmProcess.ShowDialog();
Program.mvList.SaveToFile();
UpdateGui();
}
private void btnCheckInSelected_Click(object sender, EventArgs e)
{
if (mv.Status != Mv.MvStatus.Started)
{
MessageBox.Show($"MV must be in state {Mv.MvStatus.Started}!");
return;
}
//CheckIn(lvMvInvitations.GetSelectedItems());
foreach (MvInvitedMember mvi in lvMvInvitations.GetSelectedItems())
{
mvi.AttendedMv = true;
lvMvInvitations.UpdateEntry(mvi);
}
UpdateAttendedDisplay();
}
private void btnCheckOutSelected_Click(object sender, EventArgs e)
{
if (mv.Status != Mv.MvStatus.Started)
{
MessageBox.Show($"MV must be in state {Mv.MvStatus.Started}!");
return;
}
//CheckIn(lvMvInvitations.GetSelectedItems());
foreach (MvInvitedMember mvi in lvMvInvitations.GetSelectedItems())
{
mvi.AttendedMv = false;
lvMvInvitations.UpdateEntry(mvi);
}
UpdateAttendedDisplay();
}
private void UpdateMvData()
{
DateTime dt = new DateTime( mvDate.Value.Year, mvDate.Value.Month, mvDate.Value.Day,
mvTime.Value.Hour, mvTime.Value.Minute, mvTime.Value.Second);
mv.EventDate = dt;
mv.Place = tbPlace.Text;
mv.Agenda = tbMvAgenda.Text.Replace("\r\n", "\n");
mv.InviteHeadline = tbInviteHeadline.Text;
mv.InviteBody = tbInviteBody.Text.Replace("\r\n", "\n");
mv.Protocol = tbMvProtocol.Text.Replace("\r\n", "\n");
}
private void BuildPageInvitationSettings(Control parent)
{
SplitContainer sc = new SplitContainer()
{
Orientation = Orientation.Horizontal,
Dock = DockStyle.Fill,
};
parent.Controls.Add(sc);
GroupBox details = new GroupBox()
{
Text = "Invitation details",
Dock = DockStyle.Fill,
};
sc.Panel1.Controls.Add(details);
GroupBox mail = new GroupBox()
{
Text = "Invitation Mail",
Dock = DockStyle.Fill,
};
sc.Panel2.Controls.Add(mail);
details.Controls.Add(new Label()
{
Text = "Event date+time:",
Location = new Point(lm, 0 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
details.Controls.Add(mvDate = new DateTimePicker()
{
Location = new Point(lm + 113, 0 * line + tm + groupOffset),
Width = 110,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
Format = DateTimePickerFormat.Short,
});
details.Controls.Add(mvTime = new DateTimePicker()
{
Location = new Point(lm + 243, 0 * line + tm + groupOffset),
Width = 90,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
Format = DateTimePickerFormat.Time,
});
details.Controls.Add(new Label()
{
Text = "Place:",
Location = new Point(lm, 1 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
details.Controls.Add(tbPlace = new TextBox()
{
Location = new Point(lm + 113, 1 * line + tm + groupOffset),
Width = details.ClientSize.Width - lm - 113 - groupOffset,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
});
details.Controls.Add(new Label()
{
Text = "Agenda (one item per line, auto-numbered:",
Location = new Point(lm, 2 * line + tm + labelOffs + groupOffset),
Size = new Size(320, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
details.Controls.Add(tbMvAgenda = new TextBox()
{
Location = new Point(groupOffset, 3 * line + tm + groupOffset),
Size = new Size(details.ClientSize.Width - 2 * groupOffset, details.ClientSize.Height - 3 * line - tm - 2 * groupOffset),
Multiline = true,
ScrollBars = ScrollBars.Both,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
mail.Controls.Add(new Label()
{
Text = "Headline:",
Location = new Point(lm, 0 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
mail.Controls.Add(tbInviteHeadline = new TextBox()
{
Location = new Point(lm + 113, 0 * line + tm + groupOffset),
Width = details.ClientSize.Width - lm - 113 - groupOffset,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
});
mail.Controls.Add(new Label()
{
Text = "Invitation Body:",
Location = new Point(lm, 1 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
mail.Controls.Add(tbInviteBody = new TextBox()
{
Location = new Point(groupOffset, 2 * line + tm + groupOffset),
Size = new Size(details.ClientSize.Width - 2 * groupOffset, details.ClientSize.Height - 2 * line - tm - 2 * groupOffset),
Multiline = true,
ScrollBars = ScrollBars.Both,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
}
private void BuildPageRunningMv(Control parent)
{
SplitContainer splitLR = new SplitContainer()
{
Dock = DockStyle.Fill,
};
parent.Controls.Add(splitLR);
//input field to add auth codes, with button
SplitContainer splitL_UD = new SplitContainer()
{
Orientation = Orientation.Horizontal,
Dock = DockStyle.Fill,
};
splitLR.Panel1.Controls.Add(splitL_UD);
GroupBox groupAttending = new GroupBox()
{
Text = "Attending members",
Dock = DockStyle.Fill,
Margin = new Padding(5),
};
splitL_UD.Panel1.Controls.Add(groupAttending);
GroupBox groupAttachments = new GroupBox()
{
Text = "Attachments",
Dock = DockStyle.Fill,
Margin = new Padding(5),
};
splitL_UD.Panel2.Controls.Add(groupAttachments);
GroupBox groupProtocol = new GroupBox()
{
Text = "MV protocol",
Dock = DockStyle.Fill,
};
splitLR.Panel2.Controls.Add(groupProtocol);
groupAttending.Controls.Add(new Label()
{
Text = "stats:",
Location = new Point(lm, 0 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
groupAttending.Controls.Add(lblAttendedStats = new Label()
{
Text = "",
Location = new Point(lm + 113, 0 * line + tm + labelOffs + groupOffset),
Size = new Size(110, labelHeight),
});
groupAttending.Controls.Add(new Label()
{
Text = "paste Auth Codes:",
Location = new Point(lm, 1 * line + tm + labelOffs + groupOffset),
Size = new Size(160, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
groupAttending.Controls.Add(tbAuthCodePaste = new TextBox()
{
Multiline = true,
ScrollBars = ScrollBars.Both,
Location = new Point(10, 2 * line + tm + groupOffset),
Size = new Size(groupAttending.Width - 20, groupAttending.Height - 75),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
tbAuthCodePaste.KeyPress += tbAuthCodePaste_KeyPress;
groupAttachments.Controls.Add(new LvAttachments(mv)
{
Dock = DockStyle.Fill,
});
groupProtocol.Controls.Add(tbMvProtocol = new TextBox()
{
Multiline = true,
ScrollBars = ScrollBars.Both,
Location = new Point(10, 5 + groupOffset),
Size = new Size(groupProtocol.Width - 20, groupProtocol.Height - 25),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
}
private void tbAuthCodePaste_KeyPress(object sender, KeyPressEventArgs e)
{
if (mv.Status != Mv.MvStatus.Started) return;
if (e.KeyChar != 13) return;
e.KeyChar = (char)0;
bool foundAuthCode = false;
string searchText = tbAuthCodePaste.Text.ToUpper();
foreach(MvInvitedMember mvi in mv.Members)
{
if (string.IsNullOrEmpty(mvi.AuthCode))
continue;
if (searchText.Contains(mvi.AuthCode.ToUpper()))
{
foundAuthCode = true;
mvi.AttendedMv = true;
//Add mvi to selection in list
//lvMvInvitations.SelectedItems
lvMvInvitations.UpdateEntry(mvi);
lvMvInvitations.DeSelectEntry(mvi, true);
}
}
tbAuthCodePaste.Text = "";
if(foundAuthCode)
{
}
}
private void BuildLog(Control parent)
{
parent.Controls.Add(new LogView(mv.Log)
{
Size = parent.ClientSize,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
}
private void BuildGui(Control parent)
{
int w = parent.ClientSize.Width;
int h = parent.ClientSize.Height - 35;
int tabw = w - 5, tabh = h - 25;
SplitContainer split = new SplitContainer()
{
Size = new Size(w, h),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
};
parent.Controls.Add(split);
GroupBox grpInvitations = new GroupBox()
{
Dock = DockStyle.Fill,
Text = "Invitations",
Width = 500,
//Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
};
grpInvitations.Controls.Add(lvMvInvitations = new LvMvInvitations()
{
Dock = DockStyle.Fill,
HideSelection = false,
});
split.Panel1.Controls.Add(grpInvitations);
tabControl = new TabControl()
{
Dock = DockStyle.Fill,
//Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
TabPages =
{
(tabInvitationSettings = new TabPage()
{
Text = "Invitation settings",
ClientSize = new Size(tabw, tabh),
}),
(tabRunningMv = new TabPage()
{
Text = "Running MV",
ClientSize = new Size(tabw, tabh),
}),
(tabLog = new TabPage()
{
Text = "Log",
ClientSize = new Size(tabw, tabh),
}),
}
};
split.Panel2.Controls.Add(tabControl);
BuildPageInvitationSettings(tabControl.TabPages[0]);
BuildPageRunningMv(tabControl.TabPages[1]);
BuildLog(tabControl.TabPages[2]);
}
private void Init()
{
DialogResult = DialogResult.Cancel;
this.StartPosition = FormStartPosition.CenterParent;
this.Size = new System.Drawing.Size(1200, 800);
this.Text = "dezentrale-members :: MV administration";
this.FormClosing += frmMv_Closing;
BuildGui(this);
//In order to update the tool buttons etc, we need to call UpdateGui() here
tabControl.SelectedIndexChanged += (sender, e) => { UpdateGui(); };
//Setup all controls values
mvDate.Value = mv.EventDate;
mvTime.Value = mv.EventDate;
tbPlace.Text = mv.Place;
tbMvAgenda.Text = mv.Agenda.Replace("\n", "\r\n");
tbInviteHeadline.Text = mv.InviteHeadline;
tbInviteBody.Text = mv.InviteBody.Replace("\n", "\r\n");
tbMvProtocol.Text = mv.Protocol.Replace("\n", "\r\n");
UpdateGui();
//for new MV, we want to keep the member list up-to-date in order
//to be able to invite new members even if their membership status
//becomes active after MV entry was generated. The other way round,
//entries with no (or inactive) member reference will not be
//removed (documentation purposes)
if (mv.Status < Mv.MvStatus.Started)
{
foreach (Member m in Program.members.Entries)
{
if (m.Status == Member.eStatus.Active)
{
//find invitation entry
if(mv.Members.Find((x) => x.MemberNumber == m.Number) == null)
//if there is none, create one
{
mv.Members.Add(new MvInvitedMember()
{
MemberNumber = m.Number,
});
}
}
}
}
foreach(MvInvitedMember mvi in mv.Members)
lvMvInvitations.AddEntry(mvi);
}
public frmMv(Mv mv)
{
this.mv = mv;
Init();
}
public frmMv()
{
this.mv = new Mv();
this.mv.FillDefaults();
Init();
}
private void frmMv_Closing(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
UpdateMvData();
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
}
}