// Copyright (c) 2017-present schick Informatik
// Description: The main application window
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Threading;
using System.Text;
using log4net;
using bsmd.database;
using ENI2.Controls;
using ENI2.EditControls;
using ENI2.Util;
using ENI2.Locode;
namespace ENI2
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
#region Fields
private ReportingPartyControl rpControl;
private MaerskOverviewControl moControl;
private ValueMappingsControl vmControl;
private ServerStatusControl statusControl;
private readonly SucheControl sucheControl;
private CompareExcelDialog compareExcelDialog;
private bool dbConnected;
private readonly ScaleTransform _transform = new ScaleTransform(1.0, 1.0);
private readonly Dictionary openTabs = new Dictionary();
private readonly Dictionary lockedCores = new Dictionary();
private int failedLogonCount;
private ReportingParty userEntity;
private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow));
private readonly DatabaseEntityWatchdog _dbWatchDog;
private readonly Dictionary showIdDict = new Dictionary();
private MenuItem _selectedMenuItem; // simulate radio button logic
#endregion
#region Construction
public MainWindow()
{
Thread.Sleep(500);
InitializeComponent();
App.SplashScreen.ShowMessage("loading..");
this.sucheControl = new SucheControl();
this.tabSearch.Content = this.sucheControl;
this.sucheControl.buttonSuche.IsDefault = true;
this.sucheControl.MessageCoreSelected += AnmeldungenControl_MessageCoreSelected;
this.mainPanel.LayoutTransform = this._transform;
this._dbWatchDog = new DatabaseEntityWatchdog();
this._dbWatchDog.DatabaseEntityChanged += _dbWatchDog_DatabaseEntityChanged;
this._dbWatchDog.VisitTransitIdUpdated += _dbWatchDog_VisitTransitIdUpdated;
App.SplashScreen.ShowMessage("done");
Thread.Sleep(500);
App.SplashScreen.LoadComplete();
_selectedMenuItem = menuItemNotifications;
}
#endregion
#region Search related event handler
private void AnmeldungenControl_MessageCoreSelected(MessageCore aMessageCore)
{
if(aMessageCore != null)
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.radioButton_Click(this.menuItemNotifications, null);
}));
if (!openTabs.ContainsKey(aMessageCore.Id.Value))
{
ClosableTabItem searchResultItem = new ClosableTabItem();
// try to lock the item
Guid lockedUserId = Guid.Empty;
if (!(aMessageCore.Cancelled ?? false))
{
try
{
lockedUserId = App.LockingServiceClient.Lock(aMessageCore.Id.Value, this.userEntity.Id.Value);
if (lockedUserId == Guid.Empty)
{
this.lockedCores[searchResultItem] = aMessageCore.Id.Value;
}
}
catch (Exception ex)
{
// TODO: wenn der Locking Service nicht erreichbar ist sollte das Ganze trotzdem noch irgendwie funktionieren
_log.ErrorFormat("LockingService.Lock: {0}", ex.Message);
}
}
bool iDidLockIt = (lockedUserId == Guid.Empty) && !(aMessageCore.Cancelled ?? false);
searchResultItem.TabClosing += SearchResultItem_TabClosing;
DateTime? eta = aMessageCore.IsTransit ? aMessageCore.ETAKielCanal : aMessageCore.ETA;
searchResultItem.SetHeaderText(string.Format("{0} [{1}-{2}]", aMessageCore.Shipname, aMessageCore.PoC, eta.HasValue ? eta.Value.ToShortDateString() : ""),
iDidLockIt);
searchResultItem.IsCancelled = aMessageCore.Cancelled ?? false;
DetailRootControl drc = new DetailRootControl(aMessageCore);
drc.LockedByOtherUser = !iDidLockIt;
if (!(aMessageCore.Cancelled ?? false))
{
drc.LockedBy = iDidLockIt ? this.userEntity : DBManager.Instance.GetReportingPartyDict()[lockedUserId];
}
searchResultItem.Content = drc;
this.mainFrame.Items.Add(searchResultItem);
Dispatcher.BeginInvoke((Action)(() => this.mainFrame.SelectedIndex = (this.mainFrame.Items.Count - 1)));
this.openTabs.Add(aMessageCore.Id.Value, searchResultItem);
this._dbWatchDog.Register(aMessageCore);
drc.HighlightReset += Drc_HighlightReset;
drc.OpenNewCoreRequested += (core) => this.AnmeldungenControl_MessageCoreSelected(core);
drc.ReloadCoreRequested += Drc_ReloadCoreRequested;
}
else
{
Dispatcher.BeginInvoke((Action)(() => this.mainFrame.SelectedItem = openTabs[aMessageCore.Id.Value]));
}
}
}
private void Drc_ReloadCoreRequested(Guid obj)
{
if(openTabs.ContainsKey(obj))
{
if (openTabs[obj].Content is DetailRootControl drc)
{
drc.ReloadCore();
}
}
}
private void Drc_HighlightReset(DatabaseEntity entity)
{
if (entity is MessageCore resetCore)
{
if (openTabs.ContainsKey(resetCore.Id.Value))
{
openTabs[resetCore.Id.Value].IsHighlighted = false;
}
}
}
private void SearchResultItem_TabClosing(object sender, CancelEventArgs e)
{
if (sender is ClosableTabItem tabItem)
{
DetailRootControl drc = tabItem.Content as DetailRootControl;
// Test for unsaved changes
if (drc.HasUnsavedChanges)
{
if (MessageBox.Show(Properties.Resources.textConfirmWithoutSaving, Properties.Resources.textConfirmation, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
e.Cancel = true;
}
// Test for unsent messages
List unSentMessages = drc.HasUnsentMessages;
if (!e.Cancel && (unSentMessages.Count > 0))
{
StringBuilder mBuilder = new StringBuilder();
foreach (string messageType in unSentMessages)
{
mBuilder.Append(messageType);
mBuilder.Append(" ");
}
mBuilder.AppendLine();
mBuilder.Append(Properties.Resources.textConfirmUnsentMessages);
if (MessageBox.Show(mBuilder.ToString(), Properties.Resources.textConfirmation, MessageBoxButton.YesNo,
MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
e.Cancel = true;
}
// Test for unconfirmed messages
List unConfirmedMessages = drc.HasUnConfirmedMessages;
if(!e.Cancel && (unConfirmedMessages.Count > 0))
{
StringBuilder mBuilder = new StringBuilder();
foreach (string messageType in unConfirmedMessages)
{
mBuilder.Append(messageType);
mBuilder.Append(" ");
}
mBuilder.AppendLine();
mBuilder.Append(Properties.Resources.textConfirmUnconfirmedMessages);
if (MessageBox.Show(mBuilder.ToString(), Properties.Resources.textConfirmation, MessageBoxButton.YesNo,
MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
e.Cancel = true;
}
// Dez.22: Special case for BRE/BRV: Warning if some messages are not "confirmed"
if(drc.Core.PoC.Equals("DEBRE")||drc.Core.PoC.Equals("DEBRV"))
{
if(drc.HasCriticalInfoMissing(out string missingClass))
{
_log.WarnFormat("set close warning because at least {0} is missing from BRE/BRV arrival", missingClass);
if (MessageBox.Show(string.Format(Properties.Resources.textSpecialCaseBREBRV, missingClass), Properties.Resources.textConfirmation, MessageBoxButton.YesNo,
MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
e.Cancel = true;
}
}
if (!e.Cancel)
{
if (lockedCores.ContainsKey(tabItem))
{
try
{
App.LockingServiceClient.Unlock(lockedCores[tabItem], this.userEntity.Id.Value);
lockedCores.Remove(tabItem);
}
catch (Exception ex)
{
_log.ErrorFormat("LockingService.Unlock: {0}", ex.Message);
}
}
if (openTabs.ContainsKey(drc.Core.Id.Value))
{
this._dbWatchDog.UnRegister(drc.Core);
openTabs.Remove(drc.Core.Id.Value);
}
}
}
}
#endregion
#region Window control click event handler
private void buttonAbout_Click(object sender, RoutedEventArgs e)
{
AboutDialog ad = new AboutDialog();
ad.Show();
}
private void radioButton_Click(object sender, RoutedEventArgs e)
{
if (sender == _selectedMenuItem) return; // same selected
this.rootContainer.Children.Clear();
MenuItem mi = sender as MenuItem;
_selectedMenuItem.Background = mi.Background;
_selectedMenuItem = mi;
_selectedMenuItem.Background = Brushes.LightBlue;
if(sender == this.menuItemNotifications)
{
this.rootContainer.Children.Add(this.mainFrame);
}
else if(sender == this.menuItemUserAdministration)
{
if (this.rpControl == null)
{
this.rpControl = new ReportingPartyControl();
Dictionary repPartyDict = DBManager.Instance.GetReportingPartyDict();
this.rpControl.ReportingParties = new ObservableCollection(repPartyDict.Values);
}
this.rootContainer.Children.Add(this.rpControl);
}
else if(sender == this.menuItemMaersk)
{
if (this.moControl == null)
{
this.moControl = new MaerskOverviewControl();
foreach (MaerskListControl mlc in this.moControl.ListControls)
mlc.MessageCoreSelected += this.AnmeldungenControl_MessageCoreSelected;
}
this.rootContainer.Children.Add(moControl);
}
else if(sender == this.menuItemStatus)
{
if(this.statusControl == null)
{
this.statusControl = new ServerStatusControl();
}
this.rootContainer.Children.Add(this.statusControl);
}
else if(sender == this.menuItemValueMappings)
{
if(this.vmControl == null)
{
this.vmControl = new ValueMappingsControl();
}
this.rootContainer.Children.Add(this.vmControl);
}
}
private void buttonCompareSheets_Click(object sender, RoutedEventArgs ev)
{
// Open compare dialog
if(compareExcelDialog == null)
{
this.compareExcelDialog = new CompareExcelDialog();
this.compareExcelDialog.Closed += (o, e) => this.compareExcelDialog = null;
compareExcelDialog.Show();
}
else
{
compareExcelDialog.BringUp();
}
}
private void buttonChangePassword_Click(object sender, RoutedEventArgs e)
{
ChangePasswordDialog cpd = new ChangePasswordDialog();
cpd.CurrentUser = this.userEntity;
cpd.ShowDialog();
}
#endregion
#region window lifetime event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// if (Debugger.IsAttached) this.busyIndicator.IsBusy = false; // not for me :-P
this.dbConnected = DBManager.Instance.Connect(Properties.Settings.Default.ConnectionString);
labelGeneralStatus.Text = dbConnected ? "DB Connected" : "DB Connect failed";
labelVersion.Text = "V. " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
labelUsername.Text = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Microsoft.Win32.SystemEvents.SessionEnded += SystemEvents_SessionEnded;
this.textUsername.Focus();
}
private void SystemEvents_SessionEnded(object sender, Microsoft.Win32.SessionEndedEventArgs e)
{
this.UnlockOpenCores();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
this.UnlockOpenCores();
DBManager.Instance.Disconnect();
Properties.Settings.Default.MainWindowPlacement = this.GetPlacement();
Properties.Settings.Default.Save();
Microsoft.Win32.SystemEvents.SessionEnded -= SystemEvents_SessionEnded;
}
private void Window_SourceInitialized(object sender, EventArgs e)
{
this.SetPlacement(Properties.Settings.Default.MainWindowPlacement);
this.textUsername.Focus();
}
#endregion
#region Command event handler
private void ExecutedClearCommand(object sender, ExecutedRoutedEventArgs e)
{
if (!(e.OriginalSource is Xceed.Wpf.Toolkit.DateTimePicker dtPicker))
dtPicker = CustomCommands.FindParent(e.OriginalSource as DependencyObject);
if (dtPicker != null)
{
dtPicker.Value = null;
}
// das funktioniert auch für Comboboxen :P
if (!(e.OriginalSource is ComboBox cb))
{
cb = CustomCommands.FindParent(e.OriginalSource as DependencyObject);
}
if (cb != null)
{
cb.SelectedIndex = -1;
LocalValueEnumerator localSetProperties = cb.GetLocalValueEnumerator();
while(localSetProperties.MoveNext())
{
if(localSetProperties.Current.Property.Name == "SelectedIndex")
{
cb.ClearValue(localSetProperties.Current.Property);
}
}
}
}
private void CanExecuteClearCommand(object sender, CanExecuteRoutedEventArgs e)
{
// validate?
e.CanExecute = true;
}
#endregion
#region window control events
private void buttonNewTransitIdClick(object sender, RoutedEventArgs e)
{
MessageCore newCore = new MessageCore();
VisitIdDialog visitIdDialog = new VisitIdDialog();
visitIdDialog.Core = newCore;
visitIdDialog.Closed += (senderDialog, closeArgs) =>
{
VisitIdDialog closedDialog = senderDialog as VisitIdDialog;
if(closedDialog.IsOK)
{
Util.UIHelper.SetBusyState();
if (!closedDialog.Core.IsDK)
{
// deutsche Häfen fordern eine Visit-Id an, für DK erfolgt hier nur die Anlage eines Datensatzes
closedDialog.Core.BSMDStatusInternal = MessageCore.BSMDStatus.TOSEND;
}
if (closedDialog.Core.PoC.Equals("ZZNOK"))
closedDialog.Core.IsTransit = true;
closedDialog.Core.Incoming = true;
closedDialog.Core.DefaultReportingPartyId = this.userEntity.Id;
DBManager.GetSingleCon(Properties.Settings.Default.ConnectionString).Save(closedDialog.Core);
// Meldeklassen für neuen Anlauf erzeugen
bsmd.database.Util.CreateMessagesForCore(closedDialog.Core, null, userEntity);
// watchdog registrieren
this._dbWatchDog.Register(closedDialog.Core);
// Wartedialog anzeigen
ShowIdDialog showIdDialog = new ShowIdDialog(closedDialog.Core);
showIdDialog.Closed += (sid, showIdArgs) =>
{
if (((ShowIdDialog)sid).OpenCore)
{
Dispatcher.BeginInvoke((Action)(() => {
this.AnmeldungenControl_MessageCoreSelected(closedDialog.Core); // in einem neuen Reiter öffnen
}));
}
// wenn der Dialog vorzeitig geschlossen wird erkennt man später dass man die Id dort nicht updaten braucht
if (this.showIdDict.ContainsKey(closedDialog.Core.Id.Value))
this.showIdDict[closedDialog.Core.Id.Value] = null;
this.UpdateWaitIdLabel();
};
this.showIdDict.Add(closedDialog.Core.Id.Value, showIdDialog);
showIdDialog.Show();
showIdDialog.Activate();
this.UpdateWaitIdLabel();
}
};
visitIdDialog.Show();
}
///
/// Callback Neuanlage mit vorhandener ID (zur Abfrage der verfügbaren Meldeklassen)
///
private void buttonNewWithIdClick(object sender, RoutedEventArgs e)
{
NewWithIdDialog newWithIdDialog = new NewWithIdDialog();
newWithIdDialog.OKClicked += new Action(() =>
{
if (newWithIdDialog.ValidId)
{
MessageCore newCore = new MessageCore();
newCore.Incoming = true;
newCore.InitialHIS = Message.NSWProvider.DUDR;
bool alreadyInSystem = false;
if (bsmd.database.Util.IsTransitId(newWithIdDialog.VisitTransitId))
{
newCore.TransitId = newWithIdDialog.VisitTransitId;
newCore.IsTransit = true;
alreadyInSystem = (DBManager.GetSingleCon(Properties.Settings.Default.ConnectionString).GetMessageCoreByTransitId(newWithIdDialog.VisitTransitId) != null);
}
else
{
newCore.VisitId = newWithIdDialog.VisitTransitId;
newCore.IsTransit = false;
alreadyInSystem = (DBManager.GetSingleCon(Properties.Settings.Default.ConnectionString).GetMessageCoreByVisitId(newWithIdDialog.VisitTransitId) != null);
}
if (alreadyInSystem)
{
MessageBox.Show(Properties.Resources.textVisitTransitAlreadyInDatabase, Properties.Resources.textCaptionError, MessageBoxButton.OK, MessageBoxImage.Error);
}
else
{
newCore.DefaultReportingPartyId = this.userEntity.Id;
newCore.PoC = newWithIdDialog.VisitTransitId.Substring(0, 5);
newCore.Portname = LocodeDB.PortNameFromLocode(newCore.PoC);
newCore.IMO = newWithIdDialog.IMO;
newCore.ENI = newWithIdDialog.ENI;
newCore.InitialHIS = newWithIdDialog.SelectedHIS;
if (newCore.IsTransit)
newCore.ETAKielCanal = newWithIdDialog.ETA;
else
newCore.ETA = newWithIdDialog.ETA;
DBManager.GetSingleCon(Properties.Settings.Default.ConnectionString).Save(newCore);
// Meldeklassen für neuen Anlauf erzeugen:
bsmd.database.Util.CreateMessagesForCore(newCore, null, userEntity);
this.AnmeldungenControl_MessageCoreSelected(newCore); // in einem neuen Reiter öffnen
// watchdog registrieren, damit die "grüne" Markierung erscheint, sobald die Anmeldung durch den Excel-Prozess gelaufen ist.
this._dbWatchDog.Register(newCore);
}
}
});
newWithIdDialog.Show();
}
private void closeButton_Click(object sender, RoutedEventArgs e)
{
// close particular tab
if (e.Source is TabItem tabitem)
{
this.mainFrame.Items.Remove(tabitem);
}
}
private void _dbWatchDog_DatabaseEntityChanged(DatabaseEntity entity)
{
if (entity is MessageCore changedCore)
{
// tab färben
if (this.openTabs.ContainsKey(changedCore.Id.Value))
{
TabItem tabitem = this.openTabs[changedCore.Id.Value];
this.Dispatcher.BeginInvoke(new Action(() =>
{
DetailRootControl drc = tabitem.Content as DetailRootControl;
if (tabitem != this.mainFrame.SelectedItem)
drc.ReloadCore(); // hoffentlich ist das nicht "too much"
bool respondHighlight = false;
if (changedCore.BSMDStatusInternal == MessageCore.BSMDStatus.RESPONDED)
respondHighlight = true;
if (tabitem is ClosableTabItem closableTabItem)
{
if (respondHighlight)
(closableTabItem).IsHighlightResponded = true;
else
(closableTabItem).IsHighlighted = true;
}
}));
changedCore.IsHighlighted = false;
}
}
}
private void _dbWatchDog_VisitTransitIdUpdated(DatabaseEntity entity)
{
if (entity is MessageCore changedCore)
{
if (showIdDict.ContainsKey(changedCore.Id.Value))
{
if (this.showIdDict[changedCore.Id.Value] != null)
{
this.showIdDict[changedCore.Id.Value].UpdateId(changedCore.VisitId.IsNullOrEmpty() ? changedCore.TransitId : changedCore.VisitId);
}
else
{
// Wartedialog nochmal
this.Dispatcher.Invoke(new Action(() =>
{
ShowIdDialog showIdDialog = new ShowIdDialog(changedCore);
showIdDialog.Closed += (sid, showIdArgs) =>
{
if (((ShowIdDialog)sid).OpenCore)
this.AnmeldungenControl_MessageCoreSelected(changedCore);
};
showIdDialog.Show();
showIdDialog.Activate();
showIdDialog.UpdateId(changedCore.VisitId.IsNullOrEmpty() ? changedCore.TransitId : changedCore.VisitId);
}));
}
this.showIdDict.Remove(changedCore.Id.Value);
// this._dbWatchDog.UnRegister(changedCore); // wird ggf später abgeräumt wenn der Tab geschlossen wird
this.Dispatcher.Invoke(new Action(() => UpdateWaitIdLabel()));
}
if (this.openTabs.ContainsKey(changedCore.Id.Value))
{
TabItem tabitem = this.openTabs[changedCore.Id.Value];
this.Dispatcher.BeginInvoke(new Action(() =>
{
DetailRootControl drc = tabitem.Content as DetailRootControl;
drc?.CoreChanged(changedCore);
}));
}
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
#endregion
#region mouse wheel / zooming events
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
base.OnPreviewMouseWheel(e);
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
this._transform.ScaleX += (e.Delta > 0) ? 0.05 : -0.05;
this._transform.ScaleY += (e.Delta > 0) ? 0.05 : -0.05;
}
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
if (e.ChangedButton == MouseButton.Middle)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
this._transform.ScaleX = 1.0;
this._transform.ScaleY = 1.0;
}
}
}
#endregion
#region logon box event handlers
private void buttonLogin_Click(object sender, RoutedEventArgs e)
{
if(this.textPassword.Password.IsNullOrEmpty() || this.textUsername.Text.IsNullOrEmpty())
{
this.labelLoginResult.Content = Properties.Resources.textUserNamePasswordEmpty;
return;
}
switch(ReportingParty.Login(this.textUsername.Text, this.textPassword.Password, out this.userEntity))
{
case ReportingParty.LogonResult.OK:
this.busyIndicator.IsBusy = false;
this.labelStatusBar.Text = string.Format("Rep.Party: {0} {1} [{2}]", this.userEntity.FirstName, this.userEntity.LastName, this.userEntity.Logon);
App.UserId = this.userEntity.Id;
ReportingParty.CurrentReportingParty = this.userEntity;
this.menuItemMaersk.Visibility = Visibility.Visible;
this.menuItemValueMappings.Visibility = Visibility.Visible;
if (this.userEntity.IsAdmin)
{
this.menuItemUserAdministration.Visibility = Visibility.Visible;
this.sucheControl.AdminMode = true;
}
this.menuItemValueMappings.Visibility = this.userEntity.IsEditor ? Visibility.Visible : Visibility.Hidden;
break;
case ReportingParty.LogonResult.FAILED:
this.labelLoginResult.Content = Properties.Resources.textWrongPassword;
failedLogonCount++;
break;
case ReportingParty.LogonResult.USERUKN:
this.labelLoginResult.Content = Properties.Resources.textUsernameUnknown;
failedLogonCount++;
break;
}
if (failedLogonCount == 3)
{
this.buttonLogin.IsEnabled = false;
MessageBox.Show(Properties.Resources.textWrongPasswordThreeTimes, Properties.Resources.textCaptionError);
}
}
private void buttonExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
#endregion
#region private methods
private void UnlockOpenCores()
{
try
{
// unlock all cores
foreach (ClosableTabItem tabItem in this.lockedCores.Keys)
{
App.LockingServiceClient.Unlock(lockedCores[tabItem], this.userEntity.Id.Value);
}
}
catch (Exception ex)
{
_log.ErrorFormat("LockingService.Unlock: {0}", ex.Message);
}
}
private void UpdateWaitIdLabel()
{
if(this.showIdDict.Count == 0)
{
this.labelStatusId.Header = "";
}
else
{
this.labelStatusId.Header = string.Format("waiting for {0} id(s)..", this.showIdDict.Count);
}
}
#endregion
}
}