Files
mmd-pdf/Features/Viewer/MainWindowViewerHost.cs
kua-agent 6610acfa44 feat: rebrand to MMD PDF, single corporate theme, and remove original text under an edit
- Services/PdfRedactText.cs: strip text whose origin falls inside a CoverAnnotation
  from the page content stream at save time, hooked into PdfBurn.DrawAnnotationsIntoDoc.
  Fixes edited values staying recoverable by text extraction.
- Themes/MMD.xaml replaces all thirteen themes; picker and accent strip removed; no dark mode.
- Rename KillerPDF -> MMD PDF across code, resources, packaging and locale strings; new icon.
- Remove the upstream author credit and the in-app install button.
2026-08-27 07:37:09 +02:00

242 lines
13 KiB
C#

using MmdPdf.Features;
using MmdPdf.Controls;
using System.Windows;
using System.Windows.Input;
using System;
using System.Linq;
namespace MmdPdf
{
// MainWindow's half of IViewerHost.
//
// Explicit implementation throughout, matching Shell/About.cs: MainWindow's own members are
// private, and a private member cannot implement an interface. Forwarding explicitly satisfies
// the contract without widening anything to public - a change nobody asked for.
//
// IShellServices (Window / Loc / SetStatus) is already implemented in Shell/About.cs and is
// inherited through IViewerHost, so it is deliberately NOT repeated here.
public partial class MainWindow : IViewerHost
{
void IViewerHost.MarkDirty(bool dirty) => MarkDirty(dirty);
void IViewerHost.SetTool(EditTool tool) => SetTool(tool);
bool IViewerHost.SidebarShowingOutlines => _sidebarShowingOutlines;
void IViewerHost.PopulateRecentFilesList(PdfViewer viewer) => PopulateRecentFilesList(viewer);
void IViewerHost.SwitchSidebarToPagesTab() => SwitchSidebarToPagesTab();
void IViewerHost.SyncSidebarToDocState(bool hasDoc, bool startup)
=> SyncSidebarToDocState(hasDoc, startup);
void IViewerHost.OpenFile(string path) => OpenFile(path);
void IViewerHost.UpdateFooterFade() => UpdateFooterFade();
void IViewerHost.UpdateTabStripFade() => UpdateTabStripFade();
System.Windows.Controls.Border? IViewerHost.SearchBar => _searchBar;
Features.SearchController IViewerHost.Search => Search;
System.Windows.Controls.TextBlock IViewerHost.FileNameLabel => FileNameLabel;
System.Windows.Controls.TreeView IViewerHost.OutlineTree => OutlineTree;
System.Windows.Controls.Button IViewerHost.SidebarOutlinesTab => SidebarOutlinesTab;
System.Windows.Controls.TextBlock IViewerHost.StatusText => StatusText;
FrameworkElement IViewerHost.ShortcutOverlay => ShortcutOverlay;
System.Windows.Controls.CheckBox IViewerHost.LinkConfirmCheck => LinkConfirmCheck;
System.Windows.Controls.ContextMenu IViewerHost.MakeThemedMenu() => MakeThemedMenu();
void IViewerHost.CloseSearchBar() => CloseSearchBar();
void IViewerHost.HideSignaturePopup() => HideSignaturePopup();
void IViewerHost.SaveTempAndReload(bool keepAnnotations, bool preserveZoom)
=> SaveTempAndReload(keepAnnotations, preserveZoom);
void IViewerHost.RecordNavJump() => RecordNavJump();
PageAnnotation? IViewerHost.PairPartner(PageAnnotation annotation) => PairPartner(annotation);
void IViewerHost.RenderStamps(int page) => RenderStamps(page);
void IViewerHost.OpenStampTool() => OpenStampTool();
bool IViewerHost.StampHitTest(int page, Point position) => StampHitTest(page, position);
void IViewerHost.ApplySearchHighlights(int page, System.Windows.Controls.Canvas canvas)
=> ApplySearchHighlights(page, canvas);
void IViewerHost.HighlightSearchResultsOnCurrentPage() => HighlightSearchResultsOnCurrentPage();
void IViewerHost.ShowTextSettings() => ShowTextSettings();
void IViewerHost.HideTextSettings() => HideTextSettings();
void IViewerHost.StyleEditBox(System.Windows.Controls.TextBox textBox) => StyleEditBox(textBox);
void IViewerHost.ApplyTextStyleToSelection() => ApplyTextStyleToSelection();
void IViewerHost.ShowDrawSettings(EditTool tool) => ShowDrawSettings(tool);
void IViewerHost.HideDrawSettings() => HideDrawSettings();
System.Windows.Controls.Border IViewerHost.MakeBarGrip(int dotCount) => MakeBarGrip(dotCount);
FrameworkElement IViewerHost.BuildBarHost(FrameworkElement content) => BuildBarHost(content);
void IViewerHost.PlaceAnnotationBar(System.Windows.Controls.Border bar,
System.Windows.Controls.Border grip, bool fadeIn) => PlaceAnnotationBar(bar, grip, fadeIn);
void IViewerHost.PlaceImageFromDialog(Point position, int pageIndex)
=> PlaceImageFromDialog(position, pageIndex);
void IViewerHost.PlaceSignature(Point position, int pageIndex) => PlaceSignature(position, pageIndex);
void IViewerHost.ShowSignaturePopup() => ShowSignaturePopup();
void IViewerHost.FillSignField(bool initials, int objectNumber, int pageIndex,
double x, double y, double width, double height)
=> FillSignField(initials, objectNumber, pageIndex, x, y, width, height);
void IViewerHost.ShapeToolMouseDown(int pageIndex, Point position, MouseButtonEventArgs e)
=> ShapeToolMouseDown(pageIndex, position, e);
void IViewerHost.CommitShapeDrag(int pageIndex) => CommitShapeDrag(pageIndex);
void IViewerHost.UpdateShapePolyRubber(MouseEventArgs e) => UpdateShapePolyRubber(e);
void IViewerHost.OcrRegion(int pageIndex, Rect canvasBounds) => OcrRegion(pageIndex, canvasBounds);
void IViewerHost.ShowShortcutsOverlayExclusive() => ShowShortcutsOverlayExclusive();
System.Windows.Media.SolidColorBrush IViewerHost.SwatchDimBorder => _swatchDimBorder;
PageAnnotation? IViewerHost.CloneAnnotation(PageAnnotation annotation) => CloneAnnotation(annotation);
System.Windows.TextDecorationCollection? IViewerHost.BuildDecorations(bool underline, bool strike)
=> BuildDecorations(underline, strike);
System.Windows.Media.Effects.DropShadowEffect IViewerHost.AnnotBarShadow() => AnnotBarShadow();
void IViewerHost.FadeOverlayOut(UIElement element) => FadeOverlayOut(element);
void IViewerHost.FadeOutAndRemoveBar(System.Windows.Controls.Border? bar) => FadeOutAndRemoveBar(bar);
PdfSharpCore.Pdf.PdfItem IViewerHost.DerefItem(PdfSharpCore.Pdf.PdfItem item) => DerefItem(item);
string IViewerHost.WordsToText(System.Collections.Generic.IEnumerable<UglyToad.PdfPig.Content.Word> words)
=> WordsToText(words);
System.Windows.Controls.MenuItem IViewerHost.MakeMenuItem(string header, RoutedEventHandler click,
string? gesture, string? glyph) => MakeMenuItem(header, click, gesture, glyph);
bool IViewerHost.FullScreen => _fullScreen;
bool IViewerHost.VerticalScrollVisible
{
get => _vScrollVisible;
set => _vScrollVisible = value;
}
bool IViewerHost.SpaceHeld => _spaceHeld;
void IViewerHost.RepositionAnnotationBars() => RepositionAnnotationBars();
void IViewerHost.PopulateContextMenu(PdfViewer viewer, Point point, int pageIndex)
=> ((IViewerHost)this).RunWithViewerContext(viewer, () => PopulateContextMenu(point, pageIndex));
void IViewerHost.RefreshPageList(PdfViewer viewer)
=> ((IViewerHost)this).RunWithViewerContext(viewer, RefreshPageList);
void IViewerHost.LoadOutlines(PdfViewer viewer)
=> ((IViewerHost)this).RunWithViewerContext(viewer, LoadOutlines);
Cursor IViewerHost.CursorForTool(EditTool tool) => CursorForTool(tool);
// ---- Focused-viewer notifications ----------------------------------------------------
// Single pane today, so these just drive the existing chrome directly. When there are two,
// each body gains a "is the caller the focused viewer?" guard - the sidebar, page list and
// status line follow focus rather than whichever pane happened to update last. Keeping the
// calls routed through here means that guard lands in three known places instead of being
// hunted through 84 PageList call sites. (BACKLOG.md, group C.)
void IViewerHost.ViewerPageChanged(PdfViewer viewer, int pageIndex)
{
if (!ReferenceEquals(ActiveViewer, viewer)) return;
// Direct assignment, because that is what the 84 existing call sites do - there is no
// SyncPageListSelection helper today. The guard avoids re-entering the selection
// handler when the list already agrees.
if (pageIndex < 0 || PageList is null) return;
if (PageList.SelectedIndex != pageIndex) PageList.SelectedIndex = pageIndex;
}
void IViewerHost.EnsureSidebarPageVisible(PdfViewer viewer, int pageIndex)
{
if (!ReferenceEquals(ActiveViewer, viewer) || pageIndex < 0 || pageIndex >= PageList.Items.Count) return;
PageList.ScrollIntoView(PageList.Items[pageIndex]);
}
void IViewerHost.ScrollSidebar(PdfViewer viewer, double delta)
{
if (!ReferenceEquals(ActiveViewer, viewer)) return;
var scroller = FindSidebarDescendant<System.Windows.Controls.ScrollViewer>(PageList);
scroller?.ScrollToVerticalOffset(scroller.VerticalOffset + delta);
}
void IViewerHost.ClearSidebarPages(PdfViewer viewer)
{
if (ReferenceEquals(ActiveViewer, viewer)) PageList.ItemsSource = null;
}
string IViewerHost.PageJumpText { get => _pageJumpBox.Text; set => _pageJumpBox.Text = value; }
bool IViewerHost.PageJumpEnabled { set => _pageJumpBox.IsEnabled = value; }
bool IViewerHost.CloseFileEnabled { set => _closeFileBtnRef.IsEnabled = value; }
string IViewerHost.PageTotalText { set => _pageTotalLabel.Text = value; }
void IViewerHost.SelectAllPageJumpText() => _pageJumpBox.SelectAll();
void IViewerHost.SyncZoomDisplay(string? fitTag, string displayText)
{
if (fitTag != null)
foreach (System.Windows.Controls.ComboBoxItem item in _zoomBox.Items)
if (item.Tag?.ToString() == fitTag) { _zoomBox.SelectedItem = item; return; }
foreach (System.Windows.Controls.ComboBoxItem item in _zoomBox.Items)
if (item.Content?.ToString() == displayText) { _zoomBox.SelectedItem = item; return; }
_zoomBox.SelectedItem = null;
_zoomBox.Text = displayText;
}
string? IViewerHost.SelectedZoomTag
=> (_zoomBox.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString();
void IViewerHost.CollapseZoomTextSelection()
{
if (_zoomBox.Template?.FindName("PART_EditableTextBox", _zoomBox) is System.Windows.Controls.TextBox box)
box.Select(box.Text.Length, 0);
}
// SyncZoomBox reads the current zoom itself rather than taking one, so the parameter is
// unused today. It stays in the signature because with two panes the window has to know
// WHICH viewer's zoom changed before deciding whether the toolbar box should follow.
void IViewerHost.ViewerZoomChanged(double zoomLevel) => SyncZoomBox();
void IViewerHost.ViewerFocused()
{
// Nothing to do while there is one viewer. With two panes this moves the accent halo
// here and repoints the sidebar at the caller.
}
void IViewerHost.ViewerSizeChanged(PdfViewer viewer, object sender, SizeChangedEventArgs e)
=> DocPane_SizeChanged(sender, e);
void IViewerHost.ViewerDrop(PdfViewer viewer, object sender, DragEventArgs e)
{
FocusPane(viewer);
DropZone_Drop(sender, e);
}
void IViewerHost.ViewerDragOver(object sender, DragEventArgs e)
=> DropZone_DragOver(sender, e);
void IViewerHost.ViewerDropZoneClick(object sender, MouseButtonEventArgs e)
=> DropZone_Click(sender, e);
void IViewerHost.ClearRecentFiles(object sender, MouseButtonEventArgs e)
=> RecentClearAll_Click(sender, e);
void IViewerHost.ViewerBackgroundRightClick(object sender, MouseButtonEventArgs e)
=> DocPaneBackground_RightClick(sender, e);
void IViewerHost.ViewerTabStripMouseDown(object sender, MouseButtonEventArgs e)
=> TitleBar_MouseLeftButtonDown(sender, e);
bool IViewerHost.IsViewerFocused(PdfViewer viewer)
=> ReferenceEquals(ActiveViewer, viewer);
bool IViewerHost.IsSplitView => _isSplit;
void IViewerHost.FocusViewer(PdfViewer viewer) => FocusPane(viewer);
bool IViewerHost.OtherViewerHasFile(PdfViewer viewer, string? originalFile)
{
if (string.IsNullOrEmpty(originalFile)) return false;
var other = ReferenceEquals(Viewer, viewer) ? ViewerB : Viewer;
return other.SessionsRef.Any(x => (x.Doc != null || x.DeferredPath != null)
&& string.Equals(x.OriginalFile, originalFile, StringComparison.OrdinalIgnoreCase));
}
PdfViewer? IViewerHost.TabDropTarget(PdfViewer source, MouseEventArgs e)
=> TabDropTargetPane(source, e);
void IViewerHost.UpdateTabDragFeedback(PdfViewer source,
PdfViewer.DocumentSession session, MouseEventArgs e, PdfViewer? target)
=> UpdateTabDragFeedback(source, session, e, target);
void IViewerHost.HideTabDragFeedback() => HideTabDragFeedback();
void IViewerHost.MoveTabToPane(PdfViewer source, PdfViewer target,
PdfViewer.DocumentSession session, MouseEventArgs e)
=> MoveTabToPane(source, target, session, e);
void IViewerHost.RunWithViewerContext(PdfViewer viewer, Action work)
{
var focused = ActiveViewer;
focused.CaptureActiveIfAny();
var previous = SwapActiveViewer(viewer);
try { work(); }
finally
{
SwapActiveViewer(previous);
focused.RestoreActiveFieldsOnly();
}
}
}
}