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 _ticketOverviewNotificationScopesPrimed = new HashSet(); private bool _ticketOverviewFirstEventHandled; private readonly HashSet _ticketOverviewInitWasEmptyScopes = new HashSet(); private bool _ticketOverviewAutoContinueActive; private DispatcherTimer _ticketOverviewAutoContinueFallbackTimer; private EventHandler _ticketOverviewAutoContinueCaseChangedHandler; private bool _renderTicketOverviewUserNames = false; private readonly HashSet _ticketOverviewHistoryEntries = new HashSet(); private const string DemoTicketHasDetailsInfoKey = "Demo.HasTicketDetails"; // Event zum auslösen wenn Toggle geändert wird public event EventHandler 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 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() { 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 { 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().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() { "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(value); PhoneCallSearch(searchInfo); } catch (Exception E) { LogException(E); } break; case "computerdomainsearch": try { var searchInfo = JsonConvert.DeserializeObject(value); ComputerDomainSearch(searchInfo); } catch (Exception E) { LogException(E); } break; case "usersidsearch": try { var searchInfo = JsonConvert.DeserializeObject(value); UserSidSearch(searchInfo); } catch (Exception E) { LogException(E); } break; case "ticketsearch": try { var searchInfo = JsonConvert.DeserializeObject(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 { new cHotKeyManager.cHotKeyDefinition { Id = 1, Modifier = new List { ModifierKeys.Control }, Key = Key.F3, HotKeyAction = HotKeyManager_ActivateSearch }, new cHotKeyManager.cHotKeyDefinition { Id = 2, Modifier = new List { 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.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 FilterChangesForPrimedScopes(IReadOnlyList changes) { if (changes == null || changes.Count == 0) return Array.Empty(); var filteredChanges = new List(changes.Count); var unprimedScopes = new HashSet(); var silentInitScopes = new HashSet(); 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 counts) { if (IsScopeCountsEmpty(scope, counts)) { _ticketOverviewInitWasEmptyScopes.Add(scope); } } private static bool IsScopeCountsEmpty(TileScope scope, IReadOnlyDictionary 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 GetTicketOverviewEventScopes(TicketOverviewCountsChangedEventArgs e) { if (e == null) return Enumerable.Empty(); if (e.InitializedScope.HasValue) return new[] { e.InitializedScope.Value }; if (e.Changes == null || e.Changes.Count == 0) return Enumerable.Empty(); return e.Changes.Select(change => change.Scope).Distinct(); } private static TileScope? ResolveSingleScope(IReadOnlyList 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 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(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.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(), relations, this ) { isSeen = true }; _ticketOverviewHistoryEntries.Add(entry); var lookup = relations.ToLookup( r => cF4sdIdentityEntry.GetFromSearchResult(r.Type), r => { var required = new List { 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()); } 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> LoadRelationsForTileAsync(string key, bool useRoleScope, int count) { var communication = cFasdCockpitCommunicationBase.Instance; if (communication == null) return new List(); try { var relations = await communication.GetTicketOverviewRelations(key, useRoleScope, Math.Max(0, count)).ConfigureAwait(false); var list = relations?.ToList() ?? new List(); 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(); } } private async Task PopulateTicketOverviewRelationUsersAsync(List 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(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 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 informationClasses) => Dispatcher.Invoke(() => ResultMenu.SetPendingInformationClasses(informationClasses)); public void UpdatePendingInformationClasses(HashSet informationClasses) => Dispatcher.Invoke(() => ResultMenu.UpdatePendingInformationClasses(informationClasses)); private void ResultMenu_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { var scrollViewer = FindParent((DependencyObject)sender); if (scrollViewer != null) { if (e.Delta > 0) scrollViewer.LineUp(); else scrollViewer.LineDown(); e.Handled = true; } } private T FindParent(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; } } }