using System.Diagnostics; using System.Windows; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using MmdPdf.Features; namespace MmdPdf { /// /// The About overlay's window half: the IAboutHost implementation that maps the controller's /// values onto the named XAML elements, the inline construction the card needs, and the click /// handlers. All the logic - signature, hashing, update check, self-update - lives in /// . /// /// NOTE: this stays "namespace MmdPdf" rather than MmdPdf.Shell, because it is a partial /// of MainWindow and every partial of a class must share one namespace. It moves to /// MmdPdf.Shell when MainWindow itself does. /// public partial class MainWindow : IAboutHost { private AboutController? _aboutController; private AboutController About => _aboutController ??= new AboutController(this); private void VersionLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { e.Handled = true; ShowAboutOverlay(); } private void ShowAboutOverlay() => About.Show(); private void CloseAboutOverlay() { FadeOverlayOut(AboutOverlay); } // ---- IShellServices ------------------------------------------------------------------ Window IShellServices.Window => this; // MainWindow's own Loc and SetStatus are private, and a private member cannot implement an // interface. Forwarding explicitly satisfies IShellServices without widening either of them // to public, which would be a change nobody asked for. string IShellServices.Loc(string key) => Loc(key); void IShellServices.SetStatus(string text) => SetStatus(text); // ---- IAboutHost ---------------------------------------------------------------------- string IAboutHost.Publisher { set => AboutPublisherBlock.Text = value; } string IAboutHost.Thumbprint { set => AboutThumbprintBlock.Text = value; } string IAboutHost.Sha256 { set => AboutSha256Block.Text = value; } string IAboutHost.ReleaseDate { set => AboutReleaseDateBlock.Text = value; } string IAboutHost.UpdateText { set => AboutUpdateText.Text = value; } bool IAboutHost.UpdateVisible { set => AboutUpdateButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed; } bool IAboutHost.UpdateEnabled { set => AboutUpdateButton.IsEnabled = value; } bool IAboutHost.IsDirty => _isDirty; string? IAboutHost.FileToReopen => _originalFile ?? _currentFile; /// Version line, as a hyperlink through to that release tag. void IAboutHost.SetVersion(string version) { AboutVersionBlock.Inlines.Clear(); AboutVersionBlock.Inlines.Add(AccentLink($"v{version}", () => About.OpenReleaseNotes())); } /// The AKA line. Null hides it entirely. void IAboutHost.SetAlias(string? alias) { AboutAkaBlock.Visibility = alias is null ? Visibility.Collapsed : Visibility.Visible; if (alias is null) return; AboutAkaBlock.Inlines.Clear(); AboutAkaBlock.Inlines.Add(new Run("AKA ") { Foreground = Res("MutedTextBrush") }); var hl = AccentLink(alias, () => AboutController.OpenUrl("https://thekiller.net")); hl.ToolTip = "thekiller.net"; AboutAkaBlock.Inlines.Add(hl); } /// Dismisses any other full-window overlay, then fades the card in. The overlays /// are mutually exclusive rather than stacking on top of one another. void IAboutHost.ShowCard() { if (ShortcutOverlay.Visibility == Visibility.Visible) FadeOverlayOut(ShortcutOverlay); BuildAboutStaticContent(); FadeOverlayIn(AboutOverlay); } // ---- Card content that never varies with the signature or the update state ------------ private void BuildAboutStaticContent() { // Reuse the main window's film-grain texture on the About card. if (GrainBrush?.ImageSource != null) AboutGrainBrush.ImageSource = GrainBrush.ImageSource; BuildAboutWordmark(); BuildAboutTagline(); } /// "Killer" in the text color, "PDF" in the brand green, the pair clickable. private void BuildAboutWordmark() { AboutLogoBlock.Inlines.Clear(); var hl = new Hyperlink { TextDecorations = null }; hl.Inlines.Add(new Run("MMD ") { FontFamily = UiKit.WordmarkFont, FontSize = 21, FontWeight = FontWeights.Normal, Foreground = Res("TextBrush") }); hl.Inlines.Add(new Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontSize = 27.3, Foreground = Res("AccentLogo") }); hl.Click += (_, _) => AboutController.OpenUrl("https://baobab-ts.com"); AboutLogoBlock.Inlines.Add(hl); // The shadow copy mirrors the real runs exactly (sizes, weights, fonts), so the blur // sits directly behind each letter instead of smearing above the word - a flat // single-size copy had different line metrics than the two-run wordmark. AboutLogoShadowBlock.Inlines.Clear(); var shadowBrush = new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0xB0, 0, 0, 0)); shadowBrush.Freeze(); AboutLogoShadowBlock.Inlines.Add(new Run("MMD ") { FontFamily = UiKit.WordmarkFont, FontSize = 21, FontWeight = FontWeights.Normal, Foreground = shadowBrush }); AboutLogoShadowBlock.Inlines.Add(new Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontSize = 27.3, Foreground = shadowBrush }); } /// /// Localized tagline. {0} is the (untranslated) brand, so splitting on the placeholder keeps /// "Killer Tools" a styled, clickable link while the rest translates and the brand can sit /// anywhere in the sentence the language needs it. A "\n" in the localized string marks the /// line break; the second line carries the license and the brand. /// private void BuildAboutTagline() { AboutTaglineBlock.Inlines.Clear(); var dim = Res("MutedTextBrush"); var text = Loc("Str_Tagline"); int brand = text.IndexOf("{0}", System.StringComparison.Ordinal); string pre = brand >= 0 ? text[..brand] : text; string suf = brand >= 0 ? text[(brand + 3)..] : ""; void AddText(string s) { var lines = s.Split('\n'); for (int i = 0; i < lines.Length; i++) { if (i > 0) AboutTaglineBlock.Inlines.Add(new LineBreak()); AboutTaglineBlock.Inlines.Add(new Run(lines[i]) { Foreground = dim }); } } AddText(pre); AboutTaglineBlock.Inlines.Add( AccentLink("Killer Tools", () => AboutController.OpenUrl("https://baobab-ts.com"))); AddText(suf); } // ---- Small helpers ------------------------------------------------------------------- private Brush Res(string key) => (Brush)FindResource(key); /// An accent-colored hyperlink with no underline - the family's one treatment for /// "this is clickable" on a card. private Hyperlink AccentLink(string text, System.Action onClick) { var hl = new Hyperlink(new Run(text)) { Foreground = Res("PrimaryBrush"), TextDecorations = null }; hl.Click += (_, _) => onClick(); return hl; } // ---- Handlers ------------------------------------------------------------------------ // Click the dim backdrop to dismiss; a click on the card itself is swallowed. private void AboutOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) => CloseAboutOverlay(); private void AboutOverlayCard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) => e.Handled = true; private void AboutOverlayClose_Click(object sender, RoutedEventArgs e) => CloseAboutOverlay(); private void AboutUpdateButton_Click(object sender, RoutedEventArgs e) => About.Update(); /// /// "Clear all Data" footer link: wipes settings, downloaded OCR language packs, and temp /// files after an explicit confirmation. Destructive, so it always warns first; the user's /// PDFs are untouched. /// private void AboutClearData_Click(object sender, RoutedEventArgs e) { var res = KillerDialog.Show(this, Loc("Str_ClearDataConfirm"), Loc("Str_ClearAllData"), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (res != MessageBoxResult.Yes) return; App.ClearAllData(); SetStatus(Loc("Str_St_DataCleared")); KillerDialog.Show(this, Loc("Str_ClearDataDone"), Loc("Str_ClearAllData"), MessageBoxButton.OK, MessageBoxImage.Information); } } }