1972 lines
78 KiB
C#
1972 lines
78 KiB
C#
using C4IT.FASD.Base;
|
||
using C4IT.FASD.Cockpit.Communication;
|
||
using C4IT.Logging;
|
||
using C4IT.MultiLanguage;
|
||
using FasdDesktopUi.Basics;
|
||
using FasdDesktopUi.Basics.Enums;
|
||
using FasdDesktopUi.Basics.Helper;
|
||
using FasdDesktopUi.Basics.Models;
|
||
using FasdDesktopUi.Basics.Services;
|
||
using FasdDesktopUi.Basics.Services.Models;
|
||
using FasdDesktopUi.Basics.Services.RelationService;
|
||
using FasdDesktopUi.Basics.Services.SupportCaseSearchService;
|
||
using FasdDesktopUi.Basics.UiActions;
|
||
using FasdDesktopUi.Basics.UserControls;
|
||
using Newtonsoft.Json;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Input;
|
||
using System.Windows.Interop;
|
||
using System.Windows.Media;
|
||
using System.Windows.Threading;
|
||
using static C4IT.Logging.cLogManager;
|
||
using static FasdDesktopUi.Basics.UserControls.TicketOverview;
|
||
|
||
namespace FasdDesktopUi.Pages.SearchPage
|
||
{
|
||
public partial class SearchPageView : Window, ISearchUiProvider
|
||
{
|
||
private static SearchPageView _instance = null;
|
||
private const int WM_NCHITTEST = 0x0084;
|
||
private const int HTTRANSPARENT = -1;
|
||
|
||
#region Ticketübersicht
|
||
|
||
private readonly HashSet<TileScope> _ticketOverviewNotificationScopesPrimed = new HashSet<TileScope>();
|
||
private bool _ticketOverviewFirstEventHandled;
|
||
private readonly HashSet<TileScope> _ticketOverviewInitWasEmptyScopes = new HashSet<TileScope>();
|
||
private bool _ticketOverviewAutoContinueActive;
|
||
private DispatcherTimer _ticketOverviewAutoContinueFallbackTimer;
|
||
private EventHandler _ticketOverviewAutoContinueCaseChangedHandler;
|
||
private bool _renderTicketOverviewUserNames = false;
|
||
private readonly HashSet<cSearchHistorySearchResultEntry> _ticketOverviewHistoryEntries = new HashSet<cSearchHistorySearchResultEntry>();
|
||
private const string DemoTicketHasDetailsInfoKey = "Demo.HasTicketDetails";
|
||
|
||
// Event zum auslösen wenn Toggle geändert wird
|
||
public event EventHandler<bool> FilterToggleCheckedChanged;
|
||
|
||
// Aktueller Zustand der Checkbox
|
||
public bool IsFilterChecked => FilterCheckbox.IsChecked == true;
|
||
|
||
#endregion
|
||
public static SearchPageView Instance
|
||
{
|
||
get
|
||
{
|
||
return _instance ?? (_instance = new SearchPageView());
|
||
}
|
||
}
|
||
|
||
private cF4sdPipeServer _pipeServer;
|
||
private cHotKeyManager _hotKeyManager;
|
||
|
||
private CancellationTokenSource _searchCancellationTokenSource = new CancellationTokenSource();
|
||
|
||
private bool _isActivating = false;
|
||
|
||
private cF4sdApiSearchResultRelation preSelectedRelation = null;
|
||
|
||
private readonly IRelationService _relationService = new RelationService();
|
||
public SupportCaseSearchService SearchService { get; }
|
||
|
||
private SearchPageView()
|
||
{
|
||
try
|
||
{
|
||
InitializeComponent();
|
||
SearchService = new SupportCaseSearchService(_relationService);
|
||
Visibility = Visibility.Visible;
|
||
_instance = this;
|
||
|
||
GetPrimaryScreenSize();
|
||
|
||
// FilterToggleCheckBox-Events registrieren
|
||
FilterCheckbox.Checked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, true);
|
||
FilterCheckbox.Unchecked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, false);
|
||
|
||
Loaded += (s, e) =>
|
||
{
|
||
Hide();
|
||
SearchBarUc.ActivateManualSearch();
|
||
ScheduleSearchResultMaxHeightUpdate();
|
||
};
|
||
SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
|
||
SearchBarUc.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
|
||
BodyStack_TicketOverview.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
|
||
SearchHistoryBorder.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
|
||
|
||
AddCustomEventHandlers();
|
||
|
||
if (TicketOverviewUpdateService.Instance != null)
|
||
{
|
||
TicketOverviewUpdateService.Instance.OverviewCountsChanged += TicketOverviewUpdateService_OverviewCountsChanged;
|
||
}
|
||
|
||
UiSettingsChanged(null, null);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}
|
||
|
||
public void GetPrimaryScreenSize()
|
||
{
|
||
UpdateSearchResultMaxHeight();
|
||
}
|
||
|
||
private void ScheduleSearchResultMaxHeightUpdate()
|
||
{
|
||
if (Dispatcher == null || Dispatcher.HasShutdownStarted)
|
||
return;
|
||
|
||
Dispatcher.BeginInvoke((Action)UpdateSearchResultMaxHeight, DispatcherPriority.Loaded);
|
||
}
|
||
|
||
private void UpdateSearchResultMaxHeight()
|
||
{
|
||
if (SearchResultBorder == null)
|
||
return;
|
||
|
||
var workAreaHeight = ActualHeight > 0 ? ActualHeight : SystemParameters.WorkArea.Height;
|
||
var reservedHeight = 0.0;
|
||
|
||
reservedHeight += GetVisibleHeightWithMargin(SearchBarUc);
|
||
reservedHeight += GetVisibleHeightWithMargin(BodyStack_TicketOverview);
|
||
reservedHeight += GetVisibleHeightWithMargin(SearchHistoryBorder);
|
||
|
||
if (MainBorder != null)
|
||
{
|
||
reservedHeight += MainBorder.Padding.Top + MainBorder.Padding.Bottom;
|
||
}
|
||
|
||
var searchResultMargin = SearchResultBorder.Margin;
|
||
var availableHeight = workAreaHeight - reservedHeight - searchResultMargin.Top - searchResultMargin.Bottom;
|
||
availableHeight = Math.Max(0, availableHeight);
|
||
SearchResultBorder.MaxHeight = availableHeight;
|
||
}
|
||
|
||
private static double GetVisibleHeightWithMargin(FrameworkElement element)
|
||
{
|
||
if (element == null || element.Visibility != Visibility.Visible)
|
||
return 0;
|
||
|
||
var margin = element.Margin;
|
||
return element.ActualHeight + margin.Top + margin.Bottom;
|
||
}
|
||
|
||
private void SetSearchResultVisibility(bool isVisible)
|
||
{
|
||
SearchResultBorder.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
|
||
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
|
||
ScheduleSearchResultMaxHeightUpdate();
|
||
}
|
||
|
||
public void SetSearchHistoryVisibility(bool isVisible)
|
||
{
|
||
SearchHistoryBorder.Visibility = isVisible && !SearchHistory.IsEmpty() ? Visibility.Visible : Visibility.Collapsed;
|
||
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
|
||
ScheduleSearchResultMaxHeightUpdate();
|
||
}
|
||
public void ShowLoadingTextItem(string itemText)
|
||
{
|
||
SetSearchHistoryVisibility(false);
|
||
ResultMenu.ShowLoadingTextItem(itemText);
|
||
}
|
||
|
||
public void ShowSearchRelations(cSearchHistorySearchResultEntry searchHistoryEntry, IRelationService relationService, ISearchUiProvider searchUiProvider)
|
||
{
|
||
try
|
||
{
|
||
_renderTicketOverviewUserNames = searchHistoryEntry != null && _ticketOverviewHistoryEntries.Contains(searchHistoryEntry);
|
||
Dispatcher.Invoke(() => ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: _renderTicketOverviewUserNames));
|
||
|
||
cSearchManager.ResolveRelations(searchHistoryEntry.Relations);
|
||
|
||
ILookup<enumFasdInformationClass, cMenuDataBase> relationsLookup = searchHistoryEntry.Relations
|
||
.OrderBy(r => r.UsingLevel)
|
||
.ThenBy(r => r.LastUsed)
|
||
.ToLookup(GetInformationClass, r => GetMenuData(r, relationService));
|
||
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
ResultMenu.UpdateSearchRelations(relationsLookup);
|
||
ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: _renderTicketOverviewUserNames);
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
}
|
||
|
||
enumFasdInformationClass GetInformationClass(cF4sdApiSearchResultRelation relation) => cF4sdIdentityEntry.GetFromSearchResult(relation.Type);
|
||
|
||
cMenuDataBase GetMenuData(cF4sdApiSearchResultRelation relation, IRelationService subRelationService)
|
||
{
|
||
try
|
||
{
|
||
var requiredInformationClasses = new List<enumFasdInformationClass>() { cF4sdIdentityEntry.GetFromSearchResult(relation.Type) };
|
||
bool isEnabled = cHealthCardDataHelper.HasAvailableHealthCard(requiredInformationClasses);
|
||
string disabledReason = null;
|
||
|
||
if (!isEnabled)
|
||
{
|
||
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
|
||
}
|
||
else if (ShouldDisableTicketRelationForDemo(relation))
|
||
{
|
||
isEnabled = false;
|
||
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
|
||
}
|
||
string trailingText = null;
|
||
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("UserDisplayName", out var relationUserDisplayName))
|
||
{
|
||
trailingText = relationUserDisplayName;
|
||
}
|
||
string summaryText = null;
|
||
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("Summary", out var relationSummary))
|
||
{
|
||
summaryText = relationSummary;
|
||
}
|
||
return new cMenuDataSearchRelation(relation)
|
||
{
|
||
MenuText = relation.DisplayName,
|
||
SubMenuText = summaryText,
|
||
TrailingText = trailingText,
|
||
UiAction = new cUiProcessSearchRelationAction(searchHistoryEntry, relation, relationService, searchUiProvider)
|
||
{
|
||
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
|
||
Description = isEnabled ? string.Empty : disabledReason,
|
||
AlternativeDescription = isEnabled ? string.Empty : disabledReason
|
||
}
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
// >>> Fallback NICHT null: einfacher, aktivierter Eintrag <<<
|
||
string fallbackTrailingText = null;
|
||
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("UserDisplayName", out var relationUserDisplayNameFallback))
|
||
{
|
||
fallbackTrailingText = relationUserDisplayNameFallback;
|
||
}
|
||
string fallbackSummaryText = null;
|
||
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("Summary", out var relationSummaryFallback))
|
||
{
|
||
fallbackSummaryText = relationSummaryFallback;
|
||
}
|
||
string fallbackDisabledReason = null;
|
||
bool fallbackIsEnabled = true;
|
||
try
|
||
{
|
||
var required = new List<enumFasdInformationClass> { cF4sdIdentityEntry.GetFromSearchResult(relation.Type) };
|
||
fallbackIsEnabled = cHealthCardDataHelper.HasAvailableHealthCard(required);
|
||
if (!fallbackIsEnabled)
|
||
{
|
||
fallbackDisabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
|
||
}
|
||
else if (ShouldDisableTicketRelationForDemo(relation))
|
||
{
|
||
fallbackIsEnabled = false;
|
||
fallbackDisabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
fallbackIsEnabled = true;
|
||
fallbackDisabledReason = string.Empty;
|
||
}
|
||
return new cMenuDataSearchRelation(relation)
|
||
{
|
||
MenuText = relation.DisplayName,
|
||
SubMenuText = fallbackSummaryText,
|
||
TrailingText = fallbackTrailingText,
|
||
UiAction = new cUiProcessSearchRelationAction(searchHistoryEntry, relation, relationService, searchUiProvider)
|
||
{
|
||
DisplayType = fallbackIsEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
|
||
Description = fallbackIsEnabled ? string.Empty : fallbackDisabledReason,
|
||
AlternativeDescription = fallbackIsEnabled ? string.Empty : fallbackDisabledReason
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
private void TriggeredSearch(string searchValue)
|
||
{
|
||
try
|
||
{
|
||
var visibleWindows = Application.Current.Windows.Cast<Window>().Where(window => window.IsVisible && (window is SearchPageView) == false).ToList();
|
||
visibleWindows.ForEach(window => window.Visibility = Visibility.Hidden);
|
||
SearchBarUc.SetSearchText(searchValue);
|
||
Show();
|
||
Activate();
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}
|
||
|
||
private void ShowExternalSearchInfo(string strInfo, cFasdApiSearchResultCollection resultEntry, enumF4sdSearchResultClass Class)
|
||
{
|
||
var filteredResults = new cFilteredResults(resultEntry) { AutoContinue = true };
|
||
var _t = ShowExternalSearchInfoAsync(strInfo, filteredResults, Class, suppressUi: false);
|
||
}
|
||
|
||
private async Task ShowExternalSearchInfoAsync(string strInfo, cFilteredResults result, enumF4sdSearchResultClass Class, bool suppressUi)
|
||
{
|
||
try
|
||
{
|
||
await this.Dispatcher.InvokeAsync(async () =>
|
||
{
|
||
try
|
||
{
|
||
await SearchBarUc.SetFixedSearchResultAsync(Class, strInfo, result);
|
||
|
||
if (result.AutoContinue && result.Results?.Count == 1)
|
||
{
|
||
ResultMenu.IndexOfSelectedResultItem = 0;
|
||
ResultMenu.SelectCurrentResultItem();
|
||
}
|
||
|
||
if (!suppressUi && !_ticketOverviewAutoContinueActive)
|
||
{
|
||
Show();
|
||
Activate();
|
||
}
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
});
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}
|
||
|
||
private void ActivateManualSearch()
|
||
{
|
||
CancledSearchAction();
|
||
SearchBarUc.ActivateManualSearch();
|
||
Show();
|
||
Activate();
|
||
|
||
if (cSearchManager.Instance.HistoryList.Count > 0)
|
||
{
|
||
SearchHistory.ShowSearchHistory();
|
||
SetSearchHistoryVisibility(true);
|
||
}
|
||
else
|
||
SetSearchHistoryVisibility(false);
|
||
}
|
||
|
||
public void PhoneCallSearch(cPhoneSearchParameters searchInfo)
|
||
{
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
var _h = Task.Run(async () =>
|
||
{
|
||
|
||
try
|
||
{
|
||
var _result = await cFasdCockpitCommunicationBase.Instance.GetPhoneSearchResults(searchInfo);
|
||
if (_result != null && _result.Count > 0 || cFasdCockpitConfig.Instance?.PhoneSupport?.ShowUnresolvedPhoneNumbers is true)
|
||
{
|
||
string strTxt;
|
||
if (string.IsNullOrEmpty(searchInfo.name))
|
||
strTxt = searchInfo.phone;
|
||
else
|
||
strTxt = $"{searchInfo.name} ({searchInfo.phone})";
|
||
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.Phonecall.Info"), strTxt);
|
||
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.Phone);
|
||
}
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
});
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
|
||
}
|
||
|
||
private void ComputerDomainSearch(cComputerDomainSearchParameters searchInfo)
|
||
{
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
var _h = Task.Run(async () =>
|
||
{
|
||
|
||
try
|
||
{
|
||
var _result = await cFasdCockpitCommunicationBase.Instance.GetComputerSearchResults(searchInfo.name, searchInfo.domain);
|
||
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.ComputerSearch.Info"), searchInfo.name);
|
||
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.Computer);
|
||
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
});
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
|
||
}
|
||
|
||
private void UserSidSearch(cUserSidSearchParameters searchInfo)
|
||
{
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
var _h = Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
var lstSids = searchInfo.sids.Split(',').Select((v) => v.Trim()).ToList();
|
||
var _result = await cFasdCockpitCommunicationBase.Instance.GetUserSearchResults(searchInfo.name, lstSids);
|
||
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.UserSearch.Info"), searchInfo.name);
|
||
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.User);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
});
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
|
||
}
|
||
|
||
private void TicketSearch(cTicketSearchParameters searchInfo)
|
||
{
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
if (!Guid.TryParse(searchInfo.ticketId, out var _ticketId))
|
||
return;
|
||
_ = RunTicketSearchAsync(searchInfo.ticketName, _ticketId, searchInfo.userName, searchInfo.sids);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
|
||
}
|
||
|
||
private void PipeServer_PipeMessage(string reply)
|
||
{
|
||
try
|
||
{
|
||
var type = reply;
|
||
if (type.Contains(':'))
|
||
type = reply.Substring(0, Math.Max(reply.IndexOf(':'), 0));
|
||
type = type.ToLowerInvariant();
|
||
|
||
if (string.IsNullOrWhiteSpace(type))
|
||
{
|
||
LogEntry("No valid pipe reply", LogLevels.Warning);
|
||
return;
|
||
}
|
||
|
||
if (type == "hello")
|
||
return;
|
||
|
||
if (DefaultLogger.IsDebug)
|
||
{
|
||
var _msg = new List<string>() { "pipe message received:", reply };
|
||
DefaultLogger.LogList(LogLevels.Debug, _msg);
|
||
}
|
||
|
||
if (cConnectionStatusHelper.Instance?.ApiConnectionStatus is cConnectionStatusHelper.enumOnlineStatus.offline)
|
||
return;
|
||
|
||
var value = reply.Remove(0, type.Length).TrimStart(':');
|
||
|
||
switch (type)
|
||
{
|
||
case "search":
|
||
try
|
||
{
|
||
Dispatcher.Invoke(() => TriggeredSearch(value));
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
break;
|
||
case "phonesearch":
|
||
try
|
||
{
|
||
var searchInfo = JsonConvert.DeserializeObject<cPhoneSearchParameters>(value);
|
||
PhoneCallSearch(searchInfo);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
break;
|
||
case "computerdomainsearch":
|
||
try
|
||
{
|
||
var searchInfo = JsonConvert.DeserializeObject<cComputerDomainSearchParameters>(value);
|
||
ComputerDomainSearch(searchInfo);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
break;
|
||
case "usersidsearch":
|
||
try
|
||
{
|
||
var searchInfo = JsonConvert.DeserializeObject<cUserSidSearchParameters>(value);
|
||
UserSidSearch(searchInfo);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
break;
|
||
case "ticketsearch":
|
||
try
|
||
{
|
||
var searchInfo = JsonConvert.DeserializeObject<cTicketSearchParameters>(value);
|
||
TicketSearch(searchInfo);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}
|
||
|
||
protected override void OnSourceInitialized(EventArgs e)
|
||
{
|
||
base.OnSourceInitialized(e);
|
||
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
_pipeServer = new cF4sdPipeServer();
|
||
_pipeServer.PipeMessage += PipeServer_PipeMessage;
|
||
_pipeServer.Listen(App.PipeName, MaxSize: 4096, LowerIntegrity: true);
|
||
LogEntry($"Start listening on named pipe '{App.PipeName}'...", LogLevels.Debug);
|
||
|
||
IntPtr handle = new WindowInteropHelper(this).Handle;
|
||
var hwndSource = HwndSource.FromHwnd(handle);
|
||
hwndSource.AddHook(SearchViewWindowProc);
|
||
hwndSource.AddHook(new HwndSourceHook(cUtility.WindowProc));
|
||
|
||
var hotKeyDefinitions = new List<cHotKeyManager.cHotKeyDefinition>
|
||
{
|
||
new cHotKeyManager.cHotKeyDefinition { Id = 1, Modifier = new List<ModifierKeys> { ModifierKeys.Control }, Key = Key.F3, HotKeyAction = HotKeyManager_ActivateSearch },
|
||
new cHotKeyManager.cHotKeyDefinition { Id = 2, Modifier = new List<ModifierKeys> { ModifierKeys.Control, ModifierKeys.Alt }, Key = Key.F3, HotKeyAction = HotKeyManager_CopyAndSearch }
|
||
};
|
||
|
||
#if isDemo
|
||
if (cFasdCockpitCommunicationBase.Instance?.IsDemo() == true)
|
||
{
|
||
hotKeyDefinitions.Add(new cHotKeyManager.cHotKeyDefinition
|
||
{
|
||
Id = 3,
|
||
Modifier = new List<ModifierKeys> { ModifierKeys.Control, ModifierKeys.Alt },
|
||
Key = Key.T,
|
||
HotKeyAction = () => TicketOverviewUpdateService.Instance.SimulateDemoTicket()
|
||
});
|
||
}
|
||
#endif
|
||
|
||
_hotKeyManager = new cHotKeyManager(handle, hotKeyDefinitions);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
}
|
||
|
||
public void ActivateSearchView()
|
||
{
|
||
if (_isActivating)
|
||
return;
|
||
|
||
_isActivating = true;
|
||
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
|
||
|
||
bool doShow = true;
|
||
|
||
if (doShow)
|
||
{
|
||
ActivateManualSearch();
|
||
}
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
_isActivating = false;
|
||
LogMethodEnd(CM);
|
||
}
|
||
}
|
||
|
||
private void HotKeyManager_ActivateSearch()
|
||
{
|
||
if (!IsVisible)
|
||
ActivateSearchView();
|
||
}
|
||
|
||
public void BringToFrontPreserveState()
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
if (!IsVisible)
|
||
Show();
|
||
|
||
if (WindowState == WindowState.Minimized)
|
||
WindowState = WindowState.Normal;
|
||
|
||
Activate();
|
||
Topmost = true;
|
||
Topmost = false;
|
||
Focus();
|
||
});
|
||
}
|
||
|
||
private async void HotKeyManager_CopyAndSearch()
|
||
{
|
||
try
|
||
{
|
||
bool isHotKeyDown = true;
|
||
while (isHotKeyDown)
|
||
{
|
||
isHotKeyDown = _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_F3) || _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_CONTROL) || _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_MENU);
|
||
await Task.Delay(50);
|
||
}
|
||
|
||
cUtility.SendCtrlCToCurrentWindow();
|
||
|
||
await Task.Delay(150);
|
||
|
||
var copiedText = Clipboard.GetText();
|
||
copiedText = cUtility.CleanPhoneString(copiedText);
|
||
copiedText = copiedText.Trim().Substring(0, Math.Min(copiedText.Trim().Length, 75));
|
||
|
||
ActivateSearchView();
|
||
|
||
if (copiedText != null)
|
||
{
|
||
await Dispatcher.BeginInvoke((Action)delegate
|
||
{
|
||
Activate();
|
||
SearchBarUc.SetSearchText(copiedText);
|
||
}, DispatcherPriority.Render);
|
||
}
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
|
||
}
|
||
|
||
protected override void OnClosed(EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (TicketOverviewUpdateService.Instance != null)
|
||
TicketOverviewUpdateService.Instance.OverviewCountsChanged -= TicketOverviewUpdateService_OverviewCountsChanged;
|
||
_pipeServer?.Dispose();
|
||
}
|
||
catch { }
|
||
_pipeServer = null;
|
||
|
||
_hotKeyManager?.UnRegisterHotKeys();
|
||
RemoveHandler(TicketOverview.SelectionRequestedEvent,
|
||
new TicketOverviewSelectionRequestedEventHandler(TicketOverview_SelectionRequested));
|
||
base.OnClosed(e);
|
||
}
|
||
|
||
private void AddCustomEventHandlers()
|
||
{
|
||
AddHandler(cUiActionBase.UiActionClickedEvent, new cUiActionBase.UiActionEventHandlerDelegate(UiActionWasTriggered));
|
||
SearchBarUc.CancledSearchAction = CancledSearchAction;
|
||
AddHandler(TicketOverview.SelectionRequestedEvent,
|
||
new TicketOverviewSelectionRequestedEventHandler(TicketOverview_SelectionRequested), true);
|
||
cFasdCockpitConfig.Instance.UiSettingsChanged += UiSettingsChanged;
|
||
|
||
SearchBarUc.ChangedSearchValue = filteredResults =>
|
||
{
|
||
UpdateSearchResults(filteredResults);
|
||
return Task.CompletedTask;
|
||
};
|
||
|
||
SearchBarUc.SearchValueChanged += HandleSearchValueChanged;
|
||
SearchService.RelationsFound += HandleRelationsFound;
|
||
}
|
||
|
||
private async void HandleSearchValueChanged(object sender, string searchValue)
|
||
{
|
||
const int minSearchLength = 2;
|
||
|
||
try
|
||
{
|
||
_searchCancellationTokenSource?.Cancel();
|
||
_searchCancellationTokenSource = new CancellationTokenSource();
|
||
|
||
if (string.IsNullOrWhiteSpace(searchValue))
|
||
{
|
||
SearchBarUc.SetSpinnerVisibility(Visibility.Hidden);
|
||
UpdateSearchResults(new cFilteredResults());
|
||
return;
|
||
}
|
||
|
||
if (searchValue.Length < minSearchLength)
|
||
return;
|
||
|
||
SearchBarUc.SetSpinnerVisibility(Visibility.Visible);
|
||
cFasdApiSearchResultCollection searchResults = await SupportCaseSearchService.GetSearchResultsAsync(searchValue, _searchCancellationTokenSource.Token);
|
||
SearchBarUc.SetSpinnerVisibility(Visibility.Hidden);
|
||
UpdateSearchResults(new cFilteredResults(searchResults));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
}
|
||
}
|
||
|
||
private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
var first = e.RelatedTo.FirstOrDefault();
|
||
var relationSearchResult = new cSearchHistorySearchResultEntry(first.DisplayName, first.DisplayName, e.RelatedTo.ToList(), e.StagedResultRelations.Relations.ToList(), this);
|
||
ShowSearchRelations(relationSearchResult, e.RelationService, this);
|
||
|
||
UpdatePendingInformationClasses(e.StagedResultRelations.PendingInformationClasses);
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
}
|
||
}
|
||
|
||
#region Ticketübersicht
|
||
|
||
private bool CheckTicketOverviewAvailability()
|
||
{
|
||
return cFasdCockpitConfig.Instance?.Global?.TicketConfiguration?.ShowOverview == true
|
||
&& IsTicketIntegrationActive();
|
||
}
|
||
|
||
private bool IsTicketIntegrationActive()
|
||
{
|
||
try
|
||
{
|
||
var healthCards = cF4SDCockpitXmlConfig.Instance?.HealthCardConfig?.HealthCards?.Values;
|
||
if (healthCards == null)
|
||
return false;
|
||
|
||
return healthCards.Any(card =>
|
||
card?.InformationClasses?.Contains(enumFasdInformationClass.Ticket) == true);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void UpdateTicketOverviewAvailability()
|
||
{
|
||
var enabled = CheckTicketOverviewAvailability();
|
||
var service = TicketOverviewUpdateService.Instance;
|
||
service?.UpdateAvailability(enabled);
|
||
if (enabled)
|
||
_ = service?.FetchAsync();
|
||
|
||
if (!enabled)
|
||
{
|
||
if (Dispatcher.CheckAccess())
|
||
{
|
||
ApplyTicketOverviewDisabledState();
|
||
}
|
||
else
|
||
{
|
||
Dispatcher.Invoke(ApplyTicketOverviewDisabledState);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ApplyTicketOverviewDisabledState()
|
||
{
|
||
_renderTicketOverviewUserNames = false;
|
||
_ticketOverviewNotificationScopesPrimed.Clear();
|
||
_ticketOverviewFirstEventHandled = false;
|
||
_ticketOverviewInitWasEmptyScopes.Clear();
|
||
SetTicketOverviewVisibility(false);
|
||
(Application.Current as App)?.ClearTicketOverviewTrayNotification();
|
||
}
|
||
|
||
private void SetTicketOverviewVisibility(bool isVisible)
|
||
{
|
||
var b = isVisible;
|
||
if (!CheckTicketOverviewAvailability())
|
||
b = false;
|
||
|
||
BodyStack_TicketOverview.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
TicketOverviewBorder.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
FilterCheckbox.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
RoleLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
OwnTicketsLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
TicketOverviewLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
|
||
ScheduleSearchResultMaxHeightUpdate();
|
||
}
|
||
|
||
public void ShowTicketOverviewPane()
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
bool overviewAlreadyVisible = TicketOverviewBorder.Visibility == Visibility.Visible;
|
||
|
||
SetTicketOverviewVisibility(true);
|
||
|
||
if (!overviewAlreadyVisible)
|
||
{
|
||
SetSearchResultVisibility(false);
|
||
SetSearchHistoryVisibility(false);
|
||
TicketOverviewUc?.ResetSelection();
|
||
}
|
||
TicketOverviewUc?.RefreshHighlightState(IsFilterChecked);
|
||
|
||
var app = Application.Current as FasdDesktopUi.App;
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
});
|
||
}
|
||
|
||
public void ShowTicketOverviewPaneForScope(TileScope? scope)
|
||
{
|
||
if (scope.HasValue)
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
if (TicketOverviewBorder.Visibility == Visibility.Visible)
|
||
return;
|
||
|
||
var useRoleScope = scope.Value == TileScope.Role;
|
||
if (FilterCheckbox != null && FilterCheckbox.IsChecked != useRoleScope)
|
||
FilterCheckbox.IsChecked = useRoleScope;
|
||
});
|
||
}
|
||
|
||
ShowTicketOverviewPane();
|
||
}
|
||
|
||
internal void CloseTicketOverviewResults()
|
||
{
|
||
_renderTicketOverviewUserNames = false;
|
||
ResultMenu.ShowSearchResults(new cFilteredResults(), null, this);
|
||
ResultMenu.SetHeaderText(string.Empty);
|
||
SetSearchResultVisibility(false);
|
||
SetTicketOverviewVisibility(true);
|
||
}
|
||
|
||
private void TicketOverviewUpdateService_OverviewCountsChanged(object sender, TicketOverviewCountsChangedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
ApplyLatestCounts();
|
||
var positiveChanges = e.Changes?.Where(change => change.Delta > 0).ToList();
|
||
var app = Application.Current as FasdDesktopUi.App;
|
||
var service = TicketOverviewUpdateService.Instance;
|
||
|
||
if (!_ticketOverviewFirstEventHandled)
|
||
{
|
||
_ticketOverviewFirstEventHandled = true;
|
||
foreach (var scope in GetTicketOverviewEventScopes(e))
|
||
{
|
||
PrimeTicketOverviewScope(scope);
|
||
TrackEmptyInitScope(scope, e?.CurrentCounts);
|
||
}
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
if (e.InitializedScope.HasValue)
|
||
{
|
||
PrimeTicketOverviewScope(e.InitializedScope.Value);
|
||
TrackEmptyInitScope(e.InitializedScope.Value, e?.CurrentCounts);
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
if (positiveChanges == null || positiveChanges.Count == 0)
|
||
{
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
if (service != null && !service.AreAllScopesInitialized)
|
||
{
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
var filteredChanges = FilterChangesForPrimedScopes(positiveChanges);
|
||
if (filteredChanges.Count == 0)
|
||
{
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
TicketOverviewUc?.SetHighlights(filteredChanges, IsFilterChecked);
|
||
|
||
var pendingScope = ResolveSingleScope(filteredChanges);
|
||
app?.SetTicketOverviewNotificationScope(pendingScope);
|
||
|
||
var message = BuildNotificationMessage(filteredChanges);
|
||
if (string.IsNullOrWhiteSpace(message))
|
||
{
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
return;
|
||
}
|
||
|
||
app?.ShowTicketOverviewTrayNotification(message);
|
||
ShowTicketOverviewNotification(message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
}
|
||
}
|
||
|
||
private Task TicketSearchFromOverviewRelationAsync(cF4sdApiSearchResultRelation relation)
|
||
{
|
||
if (relation == null || relation.Type != enumF4sdSearchResultClass.Ticket)
|
||
return Task.CompletedTask;
|
||
|
||
var ticketName = string.IsNullOrWhiteSpace(relation.DisplayName) ? relation.Name : relation.DisplayName;
|
||
var ticketId = relation.id;
|
||
if (ticketId == Guid.Empty)
|
||
return Task.CompletedTask;
|
||
|
||
string userName = null;
|
||
string sids = null;
|
||
|
||
if (relation.Infos != null)
|
||
{
|
||
if (!relation.Infos.TryGetValue("UserDisplayName", out userName))
|
||
relation.Infos.TryGetValue("UserAccount", out userName);
|
||
|
||
if (!relation.Infos.TryGetValue("Sids", out sids))
|
||
relation.Infos.TryGetValue("UserSid", out sids);
|
||
}
|
||
|
||
return RunTicketSearchAsync(ticketName, ticketId, userName, sids, suppressUi: true);
|
||
}
|
||
|
||
private Task RunTicketSearchAsync(string ticketName, Guid ticketId, string userName, string sids, bool suppressUi = false)
|
||
{
|
||
if (ticketId == Guid.Empty)
|
||
return Task.CompletedTask;
|
||
|
||
if (suppressUi)
|
||
BeginTicketOverviewAutoContinue(TimeSpan.FromSeconds(6));
|
||
|
||
return Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
var lstSids = sids?.Split(',').Select((v) => v.Trim()).ToList();
|
||
var _result = await cFasdCockpitCommunicationBase.Instance.GetUserSearchResults(userName, lstSids);
|
||
|
||
if (_result is null || _result.Count == 0 || _result.First().Value.Count == 0)
|
||
{
|
||
LogEntry($"No corresponding user could be found for ticket '{ticketName}'", LogLevels.Warning);
|
||
if (suppressUi)
|
||
EndTicketOverviewAutoContinue(showSearch: true);
|
||
return;
|
||
}
|
||
|
||
var userId = _result.Values.First().First().id;
|
||
if (userId == Guid.Empty)
|
||
{
|
||
LogEntry($"No valid user id could be found for ticket '{ticketName}'", LogLevels.Warning);
|
||
if (suppressUi)
|
||
EndTicketOverviewAutoContinue(showSearch: true);
|
||
return;
|
||
}
|
||
var _ticketRelation = new cF4sdApiSearchResultRelation()
|
||
{
|
||
Type = enumF4sdSearchResultClass.Ticket,
|
||
DisplayName = ticketName,
|
||
id = ticketId,
|
||
Status = enumF4sdSearchResultStatus.Active,
|
||
Identities = new cF4sdIdentityList
|
||
{
|
||
new cF4sdIdentityEntry()
|
||
{
|
||
Class = enumFasdInformationClass.User,
|
||
Id = userId
|
||
},
|
||
new cF4sdIdentityEntry()
|
||
{
|
||
Class = enumFasdInformationClass.Ticket,
|
||
Id = ticketId
|
||
},
|
||
}
|
||
};
|
||
|
||
var filteredResults = new cFilteredResults(_result) { AutoContinue = true, PreSelectedRelation = _ticketRelation };
|
||
|
||
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.TicketSearch.Info"), ticketName);
|
||
await ShowExternalSearchInfoAsync(strInfo, filteredResults, enumF4sdSearchResultClass.Ticket, suppressUi);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
if (suppressUi)
|
||
EndTicketOverviewAutoContinue(showSearch: true);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void BeginTicketOverviewAutoContinue(TimeSpan fallbackTimeout)
|
||
{
|
||
if (Dispatcher.CheckAccess())
|
||
BeginTicketOverviewAutoContinueCore(fallbackTimeout);
|
||
else
|
||
Dispatcher.Invoke(() => BeginTicketOverviewAutoContinueCore(fallbackTimeout));
|
||
}
|
||
|
||
private void BeginTicketOverviewAutoContinueCore(TimeSpan fallbackTimeout)
|
||
{
|
||
if (_ticketOverviewAutoContinueActive)
|
||
return;
|
||
|
||
_ticketOverviewAutoContinueActive = true;
|
||
|
||
SearchBarUc.IsHitTestVisible = false;
|
||
ResultMenu.IsHitTestVisible = false;
|
||
SearchHistory.IsHitTestVisible = false;
|
||
BodyStack_TicketOverview.IsHitTestVisible = false;
|
||
FilterCheckbox.IsHitTestVisible = false;
|
||
|
||
SetSearchHistoryVisibility(false);
|
||
SetSearchResultVisibility(false);
|
||
|
||
_ticketOverviewAutoContinueCaseChangedHandler = (sender, args) => EndTicketOverviewAutoContinue(showSearch: false);
|
||
cSupportCaseDataProvider.CaseChanged += _ticketOverviewAutoContinueCaseChangedHandler;
|
||
|
||
_ticketOverviewAutoContinueFallbackTimer = new DispatcherTimer { Interval = fallbackTimeout };
|
||
_ticketOverviewAutoContinueFallbackTimer.Tick += TicketOverviewAutoContinueFallbackTimer_Tick;
|
||
_ticketOverviewAutoContinueFallbackTimer.Start();
|
||
}
|
||
|
||
private void TicketOverviewAutoContinueFallbackTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
EndTicketOverviewAutoContinue(showSearch: true);
|
||
}
|
||
|
||
private void EndTicketOverviewAutoContinue(bool showSearch)
|
||
{
|
||
if (Dispatcher.CheckAccess())
|
||
EndTicketOverviewAutoContinueCore(showSearch);
|
||
else
|
||
Dispatcher.Invoke(() => EndTicketOverviewAutoContinueCore(showSearch));
|
||
}
|
||
|
||
private void EndTicketOverviewAutoContinueCore(bool showSearch)
|
||
{
|
||
if (!_ticketOverviewAutoContinueActive)
|
||
return;
|
||
|
||
_ticketOverviewAutoContinueActive = false;
|
||
|
||
if (_ticketOverviewAutoContinueFallbackTimer != null)
|
||
{
|
||
_ticketOverviewAutoContinueFallbackTimer.Stop();
|
||
_ticketOverviewAutoContinueFallbackTimer.Tick -= TicketOverviewAutoContinueFallbackTimer_Tick;
|
||
_ticketOverviewAutoContinueFallbackTimer = null;
|
||
}
|
||
|
||
if (_ticketOverviewAutoContinueCaseChangedHandler != null)
|
||
{
|
||
cSupportCaseDataProvider.CaseChanged -= _ticketOverviewAutoContinueCaseChangedHandler;
|
||
_ticketOverviewAutoContinueCaseChangedHandler = null;
|
||
}
|
||
|
||
SearchBarUc.IsHitTestVisible = true;
|
||
ResultMenu.IsHitTestVisible = true;
|
||
SearchHistory.IsHitTestVisible = true;
|
||
BodyStack_TicketOverview.IsHitTestVisible = true;
|
||
FilterCheckbox.IsHitTestVisible = true;
|
||
|
||
if (showSearch)
|
||
{
|
||
SetSearchResultVisibility(true);
|
||
Show();
|
||
Activate();
|
||
}
|
||
}
|
||
|
||
private void PrimeTicketOverviewScope(TileScope scope)
|
||
{
|
||
if (_ticketOverviewNotificationScopesPrimed.Add(scope))
|
||
{
|
||
TicketOverviewUc?.ClearHighlightsForScope(scope);
|
||
}
|
||
}
|
||
|
||
private IReadOnlyList<TileCountChange> FilterChangesForPrimedScopes(IReadOnlyList<TileCountChange> changes)
|
||
{
|
||
if (changes == null || changes.Count == 0)
|
||
return Array.Empty<TileCountChange>();
|
||
|
||
var filteredChanges = new List<TileCountChange>(changes.Count);
|
||
var unprimedScopes = new HashSet<TileScope>();
|
||
var silentInitScopes = new HashSet<TileScope>();
|
||
|
||
foreach (var change in changes)
|
||
{
|
||
if (_ticketOverviewInitWasEmptyScopes.Contains(change.Scope))
|
||
{
|
||
silentInitScopes.Add(change.Scope);
|
||
continue;
|
||
}
|
||
|
||
if (_ticketOverviewNotificationScopesPrimed.Contains(change.Scope))
|
||
{
|
||
filteredChanges.Add(change);
|
||
}
|
||
else
|
||
{
|
||
unprimedScopes.Add(change.Scope);
|
||
}
|
||
}
|
||
|
||
foreach (var scope in unprimedScopes)
|
||
{
|
||
PrimeTicketOverviewScope(scope);
|
||
}
|
||
|
||
if (silentInitScopes.Count > 0)
|
||
{
|
||
foreach (var scope in silentInitScopes)
|
||
{
|
||
_ticketOverviewInitWasEmptyScopes.Remove(scope);
|
||
PrimeTicketOverviewScope(scope);
|
||
}
|
||
}
|
||
|
||
return filteredChanges;
|
||
}
|
||
|
||
private void TrackEmptyInitScope(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
|
||
{
|
||
if (IsScopeCountsEmpty(scope, counts))
|
||
{
|
||
_ticketOverviewInitWasEmptyScopes.Add(scope);
|
||
}
|
||
}
|
||
|
||
private static bool IsScopeCountsEmpty(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
|
||
{
|
||
if (counts == null || counts.Count == 0)
|
||
return true;
|
||
|
||
foreach (var kvp in counts)
|
||
{
|
||
var value = scope == TileScope.Role ? kvp.Value.Role : kvp.Value.Personal;
|
||
if (value != 0)
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private static IEnumerable<TileScope> GetTicketOverviewEventScopes(TicketOverviewCountsChangedEventArgs e)
|
||
{
|
||
if (e == null)
|
||
return Enumerable.Empty<TileScope>();
|
||
|
||
if (e.InitializedScope.HasValue)
|
||
return new[] { e.InitializedScope.Value };
|
||
|
||
if (e.Changes == null || e.Changes.Count == 0)
|
||
return Enumerable.Empty<TileScope>();
|
||
|
||
return e.Changes.Select(change => change.Scope).Distinct();
|
||
}
|
||
|
||
private static TileScope? ResolveSingleScope(IReadOnlyList<TileCountChange> changes)
|
||
{
|
||
if (changes == null || changes.Count == 0)
|
||
return null;
|
||
|
||
var scope = changes[0].Scope;
|
||
for (int i = 1; i < changes.Count; i++)
|
||
{
|
||
if (changes[i].Scope != scope)
|
||
return null;
|
||
}
|
||
|
||
return scope;
|
||
}
|
||
|
||
private void ApplyLatestCounts()
|
||
{
|
||
var service = TicketOverviewUpdateService.Instance;
|
||
if (service == null)
|
||
return;
|
||
var counts = service.GetCountsForScope(IsFilterChecked);
|
||
if (counts == null || counts.Count == 0)
|
||
return;
|
||
|
||
TicketOverviewUc?.UpdateCounts(counts, IsFilterChecked);
|
||
TicketOverviewUc?.RefreshHighlightState(IsFilterChecked);
|
||
}
|
||
|
||
private string BuildNotificationMessage(IReadOnlyList<TileCountChange> changes)
|
||
{
|
||
if (changes == null || changes.Count == 0)
|
||
return string.Empty;
|
||
|
||
var aggregates = new List<(string RowKey, string ColumnKey, TileScope Scope, int Delta)>();
|
||
var indexMap = new Dictionary<(string RowKey, string ColumnKey, TileScope Scope), int>();
|
||
|
||
foreach (var change in changes)
|
||
{
|
||
if (!TryResolveTicketOverviewLabels(change.Key, out var rowKey, out var columnKey))
|
||
continue;
|
||
|
||
var delta = change.NewCount - change.OldCount;
|
||
if (delta == 0)
|
||
continue;
|
||
|
||
var aggregateKey = (rowKey, columnKey, change.Scope);
|
||
if (indexMap.TryGetValue(aggregateKey, out var aggregateIndex))
|
||
{
|
||
var existing = aggregates[aggregateIndex];
|
||
aggregates[aggregateIndex] = (existing.RowKey, existing.ColumnKey, existing.Scope, existing.Delta + delta);
|
||
}
|
||
else
|
||
{
|
||
indexMap[aggregateKey] = aggregates.Count;
|
||
aggregates.Add((rowKey, columnKey, change.Scope, delta));
|
||
}
|
||
}
|
||
|
||
if (aggregates.Count == 0)
|
||
return string.Empty;
|
||
|
||
var pieces = new List<string>(aggregates.Count);
|
||
foreach (var aggregate in aggregates)
|
||
{
|
||
if (aggregate.Delta == 0)
|
||
continue;
|
||
|
||
var row = cMultiLanguageSupport.GetItem(aggregate.RowKey) ?? aggregate.RowKey;
|
||
var column = cMultiLanguageSupport.GetItem(aggregate.ColumnKey) ?? aggregate.ColumnKey;
|
||
var scopeRowLabel = GetScopeRowLabel(aggregate.Scope, aggregate.RowKey, row);
|
||
var deltaText = aggregate.Delta >= 0 ? $"+{aggregate.Delta}" : aggregate.Delta.ToString();
|
||
pieces.Add($"{scopeRowLabel} – {column} ({deltaText})");
|
||
}
|
||
|
||
return string.Join(Environment.NewLine, pieces);
|
||
}
|
||
|
||
private static bool TryResolveTicketOverviewLabels(string key, out string rowKey, out string columnKey)
|
||
{
|
||
rowKey = null;
|
||
columnKey = null;
|
||
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
return false;
|
||
|
||
var normalized = key.ToLowerInvariant();
|
||
|
||
if (normalized.StartsWith("tickets"))
|
||
rowKey = "TicketOverview.Row.Heading.Tickets";
|
||
else if (normalized.StartsWith("incident"))
|
||
rowKey = "TicketOverview.Row.Heading.Incidents";
|
||
else if (normalized.StartsWith("unassigned"))
|
||
rowKey = "TicketOverview.Row.Heading.UnassignedTickets";
|
||
else
|
||
return false;
|
||
|
||
columnKey = ResolveColumnTranslationKey(normalized);
|
||
return columnKey != null;
|
||
}
|
||
|
||
private static string ResolveColumnTranslationKey(string normalizedKey)
|
||
{
|
||
if (normalizedKey.EndsWith("newinfo"))
|
||
return "TicketOverview.Column.Heading.NewInfo";
|
||
if (normalizedKey.EndsWith("critical"))
|
||
return "TicketOverview.Column.Heading.Critical";
|
||
if (normalizedKey.EndsWith("active"))
|
||
return "TicketOverview.Column.Heading.Active";
|
||
return "TicketOverview.Column.Heading.New";
|
||
}
|
||
|
||
private static string GetScopeRowLabel(TileScope scope, string rowKey, string rowLabel)
|
||
{
|
||
string suffix = null;
|
||
if (string.Equals(rowKey, "TicketOverview.Row.Heading.Tickets", StringComparison.OrdinalIgnoreCase))
|
||
suffix = "Tickets";
|
||
else if (string.Equals(rowKey, "TicketOverview.Row.Heading.Incidents", StringComparison.OrdinalIgnoreCase))
|
||
suffix = "Incidents";
|
||
else if (string.Equals(rowKey, "TicketOverview.Row.Heading.UnassignedTickets", StringComparison.OrdinalIgnoreCase))
|
||
suffix = "UnassignedTickets";
|
||
|
||
var translationKey = scope == TileScope.Role
|
||
? $"TicketOverview.ScopeRow.Role.{suffix}"
|
||
: $"TicketOverview.ScopeRow.Personal.{suffix}";
|
||
|
||
return cMultiLanguageSupport.GetItem(translationKey) ?? rowLabel;
|
||
}
|
||
|
||
private void ShowTicketOverviewNotification(string message)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(message))
|
||
return;
|
||
|
||
try
|
||
{
|
||
var notificationTitle = cMultiLanguageSupport.GetItem("TicketOverview.Notification.Title") ?? "Ticketübersicht";
|
||
var app = Application.Current as App;
|
||
app?.notifyIcon?.ShowBalloonTip(3000, notificationTitle, message, System.Windows.Forms.ToolTipIcon.Info);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
}
|
||
}
|
||
private async void TicketOverview_SelectionRequested(object sender, TicketOverviewSelectionRequestedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var app = Application.Current as FasdDesktopUi.App;
|
||
app?.ClearTicketOverviewTrayNotification();
|
||
|
||
_renderTicketOverviewUserNames = true;
|
||
SetSearchHistoryVisibility(false);
|
||
SetTicketOverviewVisibility(true);
|
||
SetSearchResultVisibility(true);
|
||
var header = BuildHeaderText(e.Key, e.UseRoleScope, e.Count);
|
||
ResultMenu.SetHeaderText(header, hideDetailsCheckbox: true);
|
||
|
||
ShowLoadingTextItem(cMultiLanguageSupport.GetItem("Searchbar.Loading.CaseData"));
|
||
|
||
SetPendingInformationClasses(new HashSet<enumFasdInformationClass> { enumFasdInformationClass.Ticket });
|
||
|
||
var relations = await LoadRelationsForTileAsync(e.Key, e.UseRoleScope, Math.Max(0, e.Count));
|
||
await PopulateTicketOverviewRelationUsersAsync(relations);
|
||
Debug.WriteLine($"[TicketOverview] Relations loaded: {relations?.Count ?? 0}");
|
||
var firstRelation = relations.FirstOrDefault();
|
||
string displayText = header;
|
||
if (firstRelation != null)
|
||
{
|
||
string firstSummary = null;
|
||
if (firstRelation.Infos != null && firstRelation.Infos.TryGetValue("Summary", out var summaryValue))
|
||
{
|
||
firstSummary = summaryValue;
|
||
}
|
||
|
||
displayText = string.IsNullOrWhiteSpace(firstSummary)
|
||
? $"{header} → {firstRelation.DisplayName}"
|
||
: $"{header} → {firstRelation.DisplayName} – {firstSummary}";
|
||
}
|
||
|
||
var entry = new cSearchHistorySearchResultEntry(
|
||
displayText,
|
||
header,
|
||
new List<cFasdApiSearchResultEntry>(),
|
||
relations,
|
||
this
|
||
)
|
||
{ isSeen = true };
|
||
_ticketOverviewHistoryEntries.Add(entry);
|
||
|
||
var lookup = relations.ToLookup(
|
||
r => cF4sdIdentityEntry.GetFromSearchResult(r.Type),
|
||
r =>
|
||
{
|
||
var required = new List<enumFasdInformationClass> { cF4sdIdentityEntry.GetFromSearchResult(r.Type) };
|
||
bool isEnabled = cHealthCardDataHelper.HasAvailableHealthCard(required);
|
||
string disabledReason = null;
|
||
if (!isEnabled)
|
||
{
|
||
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
|
||
}
|
||
else if (ShouldDisableTicketRelationForDemo(r))
|
||
{
|
||
isEnabled = false;
|
||
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
|
||
}
|
||
string trailingUser = null;
|
||
if (_renderTicketOverviewUserNames && r.Infos != null && r.Infos.TryGetValue("UserDisplayName", out var userDisplayName))
|
||
{
|
||
trailingUser = userDisplayName;
|
||
}
|
||
return (cMenuDataBase)new cMenuDataSearchRelation(r)
|
||
{
|
||
MenuText = r.DisplayName,
|
||
TrailingText = trailingUser,
|
||
UiAction = new cUiProcessTicketOverviewRelationAction(r.DisplayName, () => TicketSearchFromOverviewRelationAsync(r))
|
||
{
|
||
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
|
||
Description = isEnabled ? string.Empty : disabledReason,
|
||
AlternativeDescription = isEnabled ? string.Empty : disabledReason
|
||
}
|
||
};
|
||
}
|
||
);
|
||
ResultMenu.UpdateSearchRelations(lookup);
|
||
ResultMenu.SetHeaderText(header, hideDetailsCheckbox: true);
|
||
|
||
UpdatePendingInformationClasses(new HashSet<enumFasdInformationClass>());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
CancledSearchAction();
|
||
}
|
||
System.Diagnostics.Debug.WriteLine(
|
||
$"[TicketOverview] Key={e.Key}, UseRoleScope={e.UseRoleScope}, Count={e.Count}");
|
||
}
|
||
private string BuildHeaderText(string key, bool useRoleScope, int? count = null)
|
||
{
|
||
string scopeKey = useRoleScope ? "Searchbar.Header.Scope.Role" : "Searchbar.Header.Scope.Personal";
|
||
string entityKey = DetermineFallbackEntityKey(key);
|
||
string filterKey = DetermineFallbackFilterKey(key);
|
||
|
||
string scope = cMultiLanguageSupport.GetItem(scopeKey) ?? scopeKey;
|
||
string entity = cMultiLanguageSupport.GetItem(entityKey) ?? entityKey;
|
||
string filter = cMultiLanguageSupport.GetItem(filterKey) ?? filterKey;
|
||
string template = cMultiLanguageSupport.GetItem("Searchbar.Header.Template") ?? "{0} - {1} ({2})";
|
||
|
||
bool expectsCount = template.IndexOf("{3}", StringComparison.Ordinal) >= 0;
|
||
try
|
||
{
|
||
if (expectsCount)
|
||
{
|
||
int countValue = count ?? 0;
|
||
return string.Format(template, entity, filter, scope, countValue);
|
||
}
|
||
|
||
return string.Format(template, entity, filter, scope);
|
||
}
|
||
catch (FormatException)
|
||
{
|
||
return string.Format("{0} - {1} ({2})", entity, filter, scope);
|
||
}
|
||
}
|
||
|
||
private string DetermineFallbackEntityKey(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
return "Searchbar.Header.Entity.Tickets";
|
||
|
||
var keyLower = key.ToLowerInvariant();
|
||
return keyLower.Contains("incident")
|
||
? "Searchbar.Header.Entity.Incidents"
|
||
: "Searchbar.Header.Entity.Tickets";
|
||
}
|
||
|
||
private string DetermineFallbackFilterKey(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
return "Searchbar.Header.Filter.Overview";
|
||
|
||
var keyLower = key.ToLowerInvariant();
|
||
|
||
if (keyLower.Contains("newinfo"))
|
||
return "Searchbar.Header.Filter.NewInfo";
|
||
if (keyLower.Contains("new"))
|
||
return "Searchbar.Header.Filter.New";
|
||
if (keyLower.Contains("active"))
|
||
return "Searchbar.Header.Filter.Active";
|
||
if (keyLower.Contains("critical"))
|
||
return "Searchbar.Header.Filter.Critical";
|
||
if (keyLower.Contains("unassigned"))
|
||
return "Searchbar.Header.Filter.Unassigned";
|
||
if (keyLower.Contains("hold") || keyLower.Contains("onhold") || keyLower.Contains("wait"))
|
||
return "Searchbar.Header.Filter.OnHold";
|
||
if (keyLower.Contains("closed"))
|
||
return "Searchbar.Header.Filter.Closed";
|
||
|
||
return "Searchbar.Header.Filter.Overview";
|
||
}
|
||
|
||
private bool ShouldDisableTicketRelationForDemo(cF4sdApiSearchResultRelation relation)
|
||
{
|
||
if (relation == null)
|
||
return false;
|
||
|
||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||
if (communication?.IsDemo() != true)
|
||
return false;
|
||
|
||
if (relation.Type != enumF4sdSearchResultClass.Ticket)
|
||
return false;
|
||
|
||
if (relation.Infos == null)
|
||
return false;
|
||
|
||
if (!relation.Infos.TryGetValue(DemoTicketHasDetailsInfoKey, out var hasDetailsValue))
|
||
return false;
|
||
|
||
if (bool.TryParse(hasDetailsValue, out var hasDetails))
|
||
return !hasDetails;
|
||
|
||
return true;
|
||
}
|
||
|
||
private async Task<List<cF4sdApiSearchResultRelation>> LoadRelationsForTileAsync(string key, bool useRoleScope, int count)
|
||
{
|
||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||
if (communication == null)
|
||
return new List<cF4sdApiSearchResultRelation>();
|
||
try
|
||
{
|
||
var relations = await communication.GetTicketOverviewRelations(key, useRoleScope, Math.Max(0, count)).ConfigureAwait(false);
|
||
var list = relations?.ToList() ?? new List<cF4sdApiSearchResultRelation>();
|
||
|
||
if (TicketOverviewUpdateService.Instance != null)
|
||
foreach (var demoRelation in TicketOverviewUpdateService.Instance.GetDemoRelations(key, useRoleScope))
|
||
{
|
||
if (!list.Any(r => r.id == demoRelation.id))
|
||
list.Add(demoRelation);
|
||
}
|
||
|
||
return list;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return new List<cF4sdApiSearchResultRelation>();
|
||
}
|
||
}
|
||
|
||
private async Task PopulateTicketOverviewRelationUsersAsync(List<cF4sdApiSearchResultRelation> relations)
|
||
{
|
||
if (relations == null || relations.Count == 0)
|
||
return;
|
||
|
||
foreach (var relation in relations)
|
||
{
|
||
if (relation == null || relation.Type != enumF4sdSearchResultClass.Ticket)
|
||
continue;
|
||
|
||
if (relation.Identities == null)
|
||
relation.Identities = new cF4sdIdentityList();
|
||
|
||
var existingUsers = relation.Identities
|
||
.Where(identity => identity.Class == enumFasdInformationClass.User)
|
||
.ToList();
|
||
|
||
Guid userId = Guid.Empty;
|
||
if (relation.Infos != null)
|
||
{
|
||
if (relation.Infos.TryGetValue("Sids", out var sidsValue) ||
|
||
relation.Infos.TryGetValue("UserSid", out sidsValue))
|
||
{
|
||
var sids = sidsValue?.Split(',')
|
||
.Select(v => v.Trim())
|
||
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||
.ToList();
|
||
|
||
if (sids != null && sids.Count > 0)
|
||
{
|
||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||
if (communication != null)
|
||
{
|
||
relation.Infos.TryGetValue("UserDisplayName", out var userDisplayName);
|
||
var result = await communication.GetUserSearchResults(userDisplayName, sids);
|
||
var user = result?.Values?.FirstOrDefault()?.FirstOrDefault();
|
||
if (user != null)
|
||
userId = user.id;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (userId == Guid.Empty && relation.Infos.TryGetValue("UserAccount", out var userAccount))
|
||
{
|
||
relation.Infos.TryGetValue("UserDomain", out var userDomain);
|
||
if (!string.IsNullOrWhiteSpace(userAccount))
|
||
{
|
||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||
if (communication != null)
|
||
{
|
||
userId = await communication.GetUserIdByAccount(userAccount, userDomain ?? string.Empty);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (userId == Guid.Empty)
|
||
{
|
||
if (relation.Infos.TryGetValue("UserId", out var userIdString) ||
|
||
relation.Infos.TryGetValue("UserGuid", out userIdString) ||
|
||
relation.Infos.TryGetValue("UserIdentityId", out userIdString))
|
||
{
|
||
Guid.TryParse(userIdString, out userId);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (userId == Guid.Empty)
|
||
{
|
||
if (existingUsers.Count == 0)
|
||
continue;
|
||
|
||
if (relation.Infos != null)
|
||
{
|
||
if (!relation.Infos.ContainsKey("UserId"))
|
||
relation.Infos["UserId"] = existingUsers[0].Id.ToString();
|
||
if (!relation.Infos.ContainsKey("UserGuid"))
|
||
relation.Infos["UserGuid"] = existingUsers[0].Id.ToString();
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
if (existingUsers.Count == 0 || existingUsers.Any(identity => identity.Id != userId))
|
||
{
|
||
relation.Identities.RemoveAll(identity => identity.Class == enumFasdInformationClass.User);
|
||
relation.Identities.Add(new cF4sdIdentityEntry
|
||
{
|
||
Class = enumFasdInformationClass.User,
|
||
Id = userId
|
||
});
|
||
}
|
||
|
||
if (relation.Infos == null)
|
||
relation.Infos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
relation.Infos["UserId"] = userId.ToString();
|
||
relation.Infos["UserGuid"] = userId.ToString();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
private IntPtr SearchViewWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||
{
|
||
if (msg == WM_NCHITTEST)
|
||
{
|
||
if (!IsPointWithinInteractiveBounds(lParam))
|
||
{
|
||
handled = true;
|
||
return new IntPtr(HTTRANSPARENT);
|
||
}
|
||
}
|
||
|
||
return IntPtr.Zero;
|
||
}
|
||
|
||
private bool IsPointWithinInteractiveBounds(IntPtr lParam)
|
||
{
|
||
if (!IsLoaded || MainBorder == null || !MainBorder.IsLoaded)
|
||
return true;
|
||
|
||
if (MainBorder.ActualWidth <= 0 || MainBorder.ActualHeight <= 0)
|
||
return true;
|
||
|
||
var screenPoint = ExtractScreenPoint(lParam);
|
||
var windowPoint = PointFromScreen(screenPoint);
|
||
|
||
if (!(InputHitTest(windowPoint) is DependencyObject hitElement))
|
||
return false;
|
||
|
||
return IsDescendantOf(hitElement, MainBorder);
|
||
}
|
||
|
||
private static Point ExtractScreenPoint(IntPtr lParam)
|
||
{
|
||
int value = unchecked((int)(long)lParam);
|
||
short x = (short)(value & 0xFFFF);
|
||
short y = (short)((value >> 16) & 0xFFFF);
|
||
return new Point(x, y);
|
||
}
|
||
|
||
private static bool IsDescendantOf(DependencyObject element, DependencyObject ancestor)
|
||
{
|
||
while (element != null)
|
||
{
|
||
if (element == ancestor)
|
||
return true;
|
||
|
||
element = GetParent(element);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static DependencyObject GetParent(DependencyObject current)
|
||
{
|
||
if (current == null)
|
||
return null;
|
||
|
||
if (current is Visual || current is System.Windows.Media.Media3D.Visual3D)
|
||
return VisualTreeHelper.GetParent(current);
|
||
|
||
if (current is System.Windows.ContentElement contentElement)
|
||
return System.Windows.ContentOperations.GetParent(contentElement);
|
||
|
||
return LogicalTreeHelper.GetParent(current);
|
||
}
|
||
|
||
private void UpdateSearchResults(cFilteredResults filteredResults)
|
||
{
|
||
this.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
try
|
||
{
|
||
_renderTicketOverviewUserNames = false;
|
||
|
||
if (filteredResults?.Results is null)
|
||
{
|
||
SetSearchResultVisibility(false);
|
||
SetTicketOverviewVisibility(true);
|
||
return;
|
||
}
|
||
|
||
string menuHeaderText = filteredResults?.PreSelectedRelation?.DisplayName;
|
||
|
||
if (filteredResults?.PreSelectedRelation != null)
|
||
{
|
||
List<cFasdApiSearchResultEntry> selectedResult = filteredResults.Results.Values.FirstOrDefault();
|
||
var processSearchResult = new cUiProcessSearchResultAction(selectedResult.FirstOrDefault()?.DisplayName, this, selectedResult) { PreSelectedSearchRelation = filteredResults.PreSelectedRelation };
|
||
Dispatcher.Invoke(async () =>
|
||
{
|
||
bool isSearchOngoing = await processSearchResult.RunUiActionAsync(this, this, false, null);
|
||
if (!isSearchOngoing)
|
||
Hide();
|
||
});
|
||
return;
|
||
}
|
||
|
||
ResultMenu.ShowSearchResults(filteredResults, menuHeaderText, this);
|
||
preSelectedRelation = filteredResults.PreSelectedRelation;
|
||
|
||
SetSearchResultVisibility(true);
|
||
SetTicketOverviewVisibility(false);
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}));
|
||
}
|
||
|
||
private void CancledSearchAction()
|
||
{
|
||
_renderTicketOverviewUserNames = false;
|
||
SearchBarUc.Clear();
|
||
ResultMenu.ShowSearchResults(new cFilteredResults(), null, this);
|
||
SetSearchResultVisibility(false);
|
||
SetSearchHistoryVisibility(false);
|
||
SetTicketOverviewVisibility(true);
|
||
Visibility = Visibility.Hidden;
|
||
}
|
||
|
||
private void SlimPageWindowStateBar_ClickedClose(object sender, RoutedEventArgs e)
|
||
{
|
||
CancledSearchAction();
|
||
}
|
||
|
||
private async void UiActionWasTriggered(object sender, UiActionEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (e.UiAction == null)
|
||
return;
|
||
|
||
Mouse.OverrideCursor = Cursors.Wait;
|
||
|
||
switch (e.UiAction)
|
||
{
|
||
case cUiProcessSearchResultAction searchResultAction:
|
||
searchResultAction.PreSelectedSearchRelation = preSelectedRelation;
|
||
break;
|
||
case cUiProcessSearchRelationAction _:
|
||
case cUiProcessSearchHistoryEntry _:
|
||
case cUiProcessTicketOverviewRelationAction _:
|
||
break;
|
||
default:
|
||
var _t = e.UiAction?.GetType().Name;
|
||
Debug.Assert(true, $"The UI action '{_t}' is not supported in detailed page");
|
||
CancledSearchAction();
|
||
return;
|
||
}
|
||
|
||
SetSearchResultVisibility(true);
|
||
var actionResult = await e.UiAction.RunUiActionAsync(sender, null, false, null);
|
||
if (!(e.UiAction is cUiProcessTicketOverviewRelationAction) && !actionResult)
|
||
CancledSearchAction();
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
finally
|
||
{
|
||
Mouse.OverrideCursor = null;
|
||
}
|
||
}
|
||
|
||
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
|
||
{
|
||
switch (e.Key)
|
||
{
|
||
case Key.Up:
|
||
if (ResultMenu.IndexOfSelectedResultItem == int.MinValue)
|
||
ResultMenu.IndexOfSelectedResultItem = ResultMenu.GetSearchResultCount() - 1;
|
||
else
|
||
ResultMenu.IndexOfSelectedResultItem--;
|
||
break;
|
||
case Key.Down:
|
||
if (ResultMenu.IndexOfSelectedResultItem == int.MinValue)
|
||
ResultMenu.IndexOfSelectedResultItem = 0;
|
||
else
|
||
ResultMenu.IndexOfSelectedResultItem++;
|
||
break;
|
||
case Key.Enter:
|
||
ResultMenu.SelectCurrentResultItem();
|
||
e.Handled = true;
|
||
break;
|
||
case Key.Escape:
|
||
CancledSearchAction();
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void UiSettingsChanged(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var positionAlignement = cFasdCockpitConfig.Instance.Global.SmallViewAlignment;
|
||
|
||
switch (positionAlignement)
|
||
{
|
||
case enumF4sdHorizontalAlignment.Center:
|
||
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Center);
|
||
break;
|
||
case enumF4sdHorizontalAlignment.Right:
|
||
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Right);
|
||
break;
|
||
default:
|
||
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Left);
|
||
break;
|
||
}
|
||
|
||
UpdateTicketOverviewAvailability();
|
||
}
|
||
catch (Exception E)
|
||
{
|
||
LogException(E);
|
||
}
|
||
}
|
||
|
||
public void SetPendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.SetPendingInformationClasses(informationClasses));
|
||
|
||
public void UpdatePendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.UpdatePendingInformationClasses(informationClasses));
|
||
|
||
private void ResultMenu_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||
{
|
||
var scrollViewer = FindParent<ScrollViewer>((DependencyObject)sender);
|
||
|
||
if (scrollViewer != null)
|
||
{
|
||
if (e.Delta > 0)
|
||
scrollViewer.LineUp();
|
||
else
|
||
scrollViewer.LineDown();
|
||
|
||
e.Handled = true;
|
||
}
|
||
}
|
||
|
||
private T FindParent<T>(DependencyObject child) where T : DependencyObject
|
||
{
|
||
DependencyObject parent = VisualTreeHelper.GetParent(child);
|
||
|
||
while (parent != null)
|
||
{
|
||
if (parent is T typed)
|
||
return typed;
|
||
|
||
parent = VisualTreeHelper.GetParent(parent);
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
}
|