vendor: import KillerPDF 1.7.5 source (GPL-3.0) as the base for MMD PDF

This commit is contained in:
2026-08-27 06:58:22 +02:00
commit 532485a830
577 changed files with 149058 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using KillerPDF.Controls;
namespace KillerPDF.Features
{
/// <summary>
/// What a document viewer needs from the window around it.
///
/// EXTENDS IShellServices, per the family rule in that file - the shell implements Window /
/// Loc / SetStatus once, not once per feature. Those three cover the viewer's two heaviest
/// call groups on their own (Loc 70 uses, SetStatus 68) plus the modal-dialog owner that
/// TextEditing and Links need for KillerDialog (3 uses).
///
/// DERIVED FROM MEASUREMENT, not guessed: every member here came from grepping what the nine
/// files bound for the viewer control (Viewport, Zoom, Annotations, Selection, TextEditing,
/// Crop, Links, Forms, PageSelection) actually reach for on MainWindow today. Use counts are
/// noted per member so the cost of each is visible.
///
/// That audit split the coupling into three groups, and only the first belongs here:
///
/// A. HOST SERVICES - chrome and app-level services the viewer asks for. This interface.
///
/// B. PER-DOCUMENT STATE - _doc (120 uses), _currentFile (33), _annotations (56),
/// _renderDims (34), _pageRotations (11), _undoStack (4). These are NOT host services:
/// they already ride in DocumentSession, which tab switching swaps by reference. The
/// viewer will hold its own active session and read them from it. Routing them through
/// the host would be a mistake that undoes the session design.
///
/// C. PageList - 84 uses, the single biggest coupling, and a DESIGN DECISION rather than a
/// mechanical one. The sidebar's page-thumbnail list is window chrome, but the viewer
/// drives it constantly (selection sync, scroll-to-page). With two panes there is still
/// ONE sidebar, so it has to follow the FOCUSED pane. The viewer therefore must not touch
/// PageList directly; it raises the notifications below and the window decides whether
/// this viewer is the focused one before acting. Getting this wrong is how the two panes
/// would end up fighting over the sidebar.
/// </summary>
internal interface IViewerHost : IShellServices
{
// ── Chrome the viewer updates (group A) ─────────────────────────────────────────────
/// <summary>Mark the active document dirty (unsaved changes). 32 uses. Stays a host
/// service even though dirtiness is per-document, because it also drives window chrome -
/// the tab's dirty dot and the title bar.</summary>
void MarkDirty(bool dirty = true);
// PushUndo is deliberately NOT here, despite 9 uses. Undo is per-document state (group B):
// _undoStack rides in DocumentSession, so the viewer pushes onto the session it is showing
// rather than asking the window. It was in this interface briefly and the compiler caught
// it - UndoEntry is a private nested record struct on MainWindow, and widening it plus its
// UndoKind enum just to satisfy the signature would have been the wrong fix for a member
// that should not have been here. (2026-08-01.)
/// <summary>Switch tools - Crop uses this to drop back to Select when it finishes. 2 uses.</summary>
void SetTool(EditTool tool);
bool SidebarShowingOutlines { get; }
void PopulateRecentFilesList(PdfViewer viewer);
void SwitchSidebarToPagesTab();
void SyncSidebarToDocState(bool hasDoc, bool startup);
void OpenFile(string path);
void UpdateFooterFade();
void UpdateTabStripFade();
Border? SearchBar { get; }
SearchController Search { get; }
TextBlock FileNameLabel { get; }
TreeView OutlineTree { get; }
Button SidebarOutlinesTab { get; }
TextBlock StatusText { get; }
FrameworkElement ShortcutOverlay { get; }
CheckBox LinkConfirmCheck { get; }
ContextMenu MakeThemedMenu();
void CloseSearchBar();
void HideSignaturePopup();
void SaveTempAndReload(bool keepAnnotations, bool preserveZoom);
void RecordNavJump();
PageAnnotation? PairPartner(PageAnnotation annotation);
void RenderStamps(int page);
void OpenStampTool();
bool StampHitTest(int page, Point position);
void ApplySearchHighlights(int page, Canvas canvas);
void HighlightSearchResultsOnCurrentPage();
void ShowTextSettings();
void HideTextSettings();
void StyleEditBox(TextBox textBox);
void ApplyTextStyleToSelection();
void ShowDrawSettings(EditTool tool);
void HideDrawSettings();
Border MakeBarGrip(int dotCount);
FrameworkElement BuildBarHost(FrameworkElement content);
void PlaceAnnotationBar(Border bar, Border grip, bool fadeIn);
void PlaceImageFromDialog(Point position, int pageIndex);
void PlaceSignature(Point position, int pageIndex);
void ShowSignaturePopup();
void FillSignField(bool initials, int objectNumber, int pageIndex,
double x, double y, double width, double height);
void ShapeToolMouseDown(int pageIndex, Point position, MouseButtonEventArgs e);
void CommitShapeDrag(int pageIndex);
void UpdateShapePolyRubber(MouseEventArgs e);
void OcrRegion(int pageIndex, Rect canvasBounds);
void ShowShortcutsOverlayExclusive();
System.Windows.Media.SolidColorBrush SwatchDimBorder { get; }
PageAnnotation? CloneAnnotation(PageAnnotation annotation);
System.Windows.TextDecorationCollection? BuildDecorations(bool underline, bool strike);
System.Windows.Media.Effects.DropShadowEffect AnnotBarShadow();
void FadeOverlayOut(UIElement element);
void FadeOutAndRemoveBar(Border? bar);
PdfSharpCore.Pdf.PdfItem DerefItem(PdfSharpCore.Pdf.PdfItem item);
string WordsToText(System.Collections.Generic.IEnumerable<UglyToad.PdfPig.Content.Word> words);
MenuItem MakeMenuItem(string header, RoutedEventHandler click, string? gesture, string? glyph);
bool FullScreen { get; }
bool VerticalScrollVisible { get; set; }
bool SpaceHeld { get; }
void RepositionAnnotationBars();
void PopulateContextMenu(PdfViewer viewer, Point point, int pageIndex);
void RefreshPageList(PdfViewer viewer);
void LoadOutlines(PdfViewer viewer);
Cursor CursorForTool(EditTool tool);
// ── Notifications, so the window can update chrome for the FOCUSED viewer only ───────
// These replace the viewer poking at PageList / ZoomBox / PageLabel / StatusText itself.
/// <summary>This viewer scrolled or paged to a different page.</summary>
void ViewerPageChanged(PdfViewer viewer, int pageIndex);
void EnsureSidebarPageVisible(PdfViewer viewer, int pageIndex);
void ScrollSidebar(PdfViewer viewer, double delta);
void ClearSidebarPages(PdfViewer viewer);
string PageJumpText { get; set; }
bool PageJumpEnabled { set; }
bool CloseFileEnabled { set; }
string PageTotalText { set; }
void SelectAllPageJumpText();
void SyncZoomDisplay(string? fitTag, string displayText);
string? SelectedZoomTag { get; }
void CollapseZoomTextSelection();
/// <summary>This viewer's zoom or fit mode changed (updates the zoom box). 16 uses of
/// ZoomBox today.</summary>
void ViewerZoomChanged(double zoomLevel);
/// <summary>This viewer took focus - the window repoints the sidebar, page list and
/// status line at it, and moves the accent halo.</summary>
void ViewerFocused();
// Window-owned chrome and start-screen actions raised by one viewer instance.
void ViewerSizeChanged(PdfViewer viewer, object sender, SizeChangedEventArgs e);
void ViewerDrop(PdfViewer viewer, object sender, DragEventArgs e);
void ViewerDragOver(object sender, DragEventArgs e);
void ViewerDropZoneClick(object sender, MouseButtonEventArgs e);
void ClearRecentFiles(object sender, MouseButtonEventArgs e);
void ViewerBackgroundRightClick(object sender, MouseButtonEventArgs e);
void ViewerTabStripMouseDown(object sender, MouseButtonEventArgs e);
bool IsViewerFocused(PdfViewer viewer);
bool IsSplitView { get; }
void FocusViewer(PdfViewer viewer);
bool OtherViewerHasFile(PdfViewer viewer, string? originalFile);
PdfViewer? TabDropTarget(PdfViewer source, MouseEventArgs e);
void UpdateTabDragFeedback(PdfViewer source, PdfViewer.DocumentSession session,
MouseEventArgs e, PdfViewer? target);
void HideTabDragFeedback();
void MoveTabToPane(PdfViewer source, PdfViewer target,
PdfViewer.DocumentSession session, MouseEventArgs e);
void RunWithViewerContext(PdfViewer viewer, System.Action work);
}
}
+97
View File
@@ -0,0 +1,97 @@
using System.Collections.Generic;
namespace KillerPDF
{
/// <summary>
/// The tab and session members under the names the window already calls them by, routed to the
/// focused pane. Keeping the old names resolvable leaves the call sites in FileOperations,
/// KeyboardShortcuts, ImportAndZip, TempReload, WindowChrome and SettingsPanel unchanged while
/// making them act on whichever pane has focus.
///
/// Ctrl+W, Ctrl+Tab, Ctrl+Q and CloseFile_Click are keyboard- or XAML-bound, so these
/// declarations are load-bearing rather than convenience.
/// </summary>
public partial class MainWindow
{
private void OpenInNewTab(string path) => ActiveViewer.OpenInNewTabExt(path);
private void CloseTab(Controls.PdfViewer.DocumentSession? s) => ActiveViewer.CloseTabExt(s);
private void CloseAllTabs() => ActiveViewer.CloseAllTabsExt();
private void CloseOtherTabs(Controls.PdfViewer.DocumentSession? s) => ActiveViewer.CloseOtherTabsExt(s);
private void CycleTab(int dir) => ActiveViewer.CycleTabExt(dir);
private void EnsureInitialSession() => ActiveViewer.EnsureInitialSessionExt();
private void MaterializeDeferred(Controls.PdfViewer.DocumentSession target)
=> ActiveViewer.MaterializeDeferredExt(target);
private Controls.PdfViewer.DocumentSession BeginTabLoad(
out Controls.PdfViewer.DocumentSession? prev, out bool createdNew)
=> ActiveViewer.BeginTabLoadExt(out prev, out createdNew);
private void AbortTabLoad(Controls.PdfViewer.DocumentSession target,
Controls.PdfViewer.DocumentSession? prev, bool createdNew)
=> ActiveViewer.AbortTabLoadExt(target, prev, createdNew);
private void CaptureSessionState(Controls.PdfViewer.DocumentSession s)
=> ActiveViewer.CaptureSessionStateExt(s);
private void ApplySessionState(Controls.PdfViewer.DocumentSession s)
=> ActiveViewer.ApplySessionStateExt(s);
private void SaveDocState(string? path, FitMode fit, double zoom, ViewMode view, int page)
=> ActiveViewer.SaveDocStateExt(path, fit, zoom, view, page);
private bool TryGetDocState(string? path, out FitMode fit, out double zoom,
out ViewMode view, out int page)
=> ActiveViewer.TryGetDocStateExt(path, out fit, out zoom, out view, out page);
private void RebuildTabStrip() => ActiveViewer.RebuildTabStripExt();
private void ScheduleTabReflow() => ActiveViewer.ScheduleTabReflowExt();
private void RenderActiveSession() => ActiveViewer.RenderActiveSessionExt();
private void ShowEmptyState() => ActiveViewer.ShowEmptyStateExt();
/// <summary>Night mode changed: flush both panes. The invert state is baked into cached
/// pixels, so pane B's cache is as stale as pane A's.</summary>
private void FlushAllRenderCaches()
{
Viewer.FlushAllRenderCachesExt();
ViewerB.FlushAllRenderCachesExt();
}
/// <summary>Every open document across both panes. The quit prompt and the settings writer
/// need all of them, or closing the window silently drops pane B's unsaved work.</summary>
private IEnumerable<Controls.PdfViewer.DocumentSession> AllSessions()
{
foreach (var s in Viewer.SessionsRef) yield return s;
foreach (var s in ViewerB.SessionsRef) yield return s;
}
/// <summary>The focused pane's open documents. Callers that mean "this pane" - the tab
/// context menu's Close Others, the reorder resync - want this rather than AllSessions.</summary>
private System.Collections.ObjectModel.ObservableCollection<Controls.PdfViewer.DocumentSession> _sessions
=> ActiveViewer.SessionsRef;
/// <summary>The focused pane's tab strip, for the chrome that positions or hides it.
/// AppScale and FullScreen act on both panes at their own call sites; SidebarLayout's fade
/// mask only describes the pane it is measuring, so it takes the active one.</summary>
private System.Windows.Controls.Border TabStripBorder => ActiveViewer.TabStripBorderCtl;
private System.Windows.Controls.Border TabStripFade => ActiveViewer.TabStripFadeCtl;
/// <summary>True when the other pane holds an unsaved copy of the same file.
///
/// The split opens a file as two independent copies, not two views of one document: each
/// pane has its own annotations, undo stack and dirty flag, so whichever saves last wins.
/// This is the guard on that.
///
/// Compares OriginalFile, not CurrentFile: crop and rotate swap the working file out to a
/// temp path, so CurrentFile can differ between two panes showing the same document.</summary>
private bool OtherPaneHasDirtyCopyOf(string? originalFile)
{
if (string.IsNullOrEmpty(originalFile)) return false;
var other = ReferenceEquals(ActiveViewer, Viewer) ? ViewerB : Viewer;
other.CaptureActiveIfAny(); // its live dirty flag may not be folded into its session yet
return other.SessionsRef.Any(s => s.IsDirty
&& string.Equals(s.OriginalFile, originalFile, System.StringComparison.OrdinalIgnoreCase));
}
/// <summary>Place the restored session list into pane A. The startup restore builds the list
/// itself rather than going through EnsureInitialSession, so it hands the result over.
/// Per-pane restore is not implemented; everything reopens in pane A.</summary>
private void SetRestoredSessions(IEnumerable<Controls.PdfViewer.DocumentSession> sessions,
Controls.PdfViewer.DocumentSession? active)
=> Viewer.SetSessionsExt(sessions, active);
}
}
+111
View File
@@ -0,0 +1,111 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using PdfSharpCore.Pdf;
namespace KillerPDF
{
/// <summary>
/// The window's half of the viewer bridge. Read this alongside
/// Controls/PdfViewer.Bridge.cs, which is the other end of every line here.
///
/// TWO DIRECTIONS, and they are kept apart on purpose:
///
/// INWARD - accessors the viewer reads. MainWindow's own members are private and a control
/// in another namespace cannot see them. Rather than widen ~40 fields in place and
/// scatter `internal` through fifteen files, each is exposed once, here, under a
/// name that says it is a bridge rather than an ordinary member. The private
/// fields stay private, so nothing else in the app gains reach by accident.
///
/// OUTWARD - focused-pane routing for shared toolbar and keyboard commands. Per-viewer
/// editing and document state stays on PdfViewer; only window-owned commands
/// cross this boundary.
///
/// XAML handlers remain on MainWindow because WPF resolves them against the XAML root.
/// </summary>
public partial class MainWindow
{
// ══ INWARD: chrome the viewer reads ═════════════════════════════════════════════════
// PageList is not here - x:Name fields are generated internal, so the control already
// sees it. These four are hand-declared private fields assigned from FindName, so they
// are not.
/// <summary>The window's ONE PageList selection delegate. Handed out rather than rebuilt
/// because SyncCurrentPageTo detaches and reattaches it - a method group would make a new
/// delegate per call and the -= would quietly remove nothing.</summary>
internal SelectionChangedEventHandler PageListSelectionHandler
=> _pageListSelectionHandler ??= PageList_SelectionChanged;
private SelectionChangedEventHandler? _pageListSelectionHandler;
// ══ INWARD: per-document state (group B - goes when the viewer owns its session) ═════
// Settable: the xref-repair path in Annotations.cs, which lives in the viewer, reopens the
// document and re-points the temp file.
// Reads OUT of the focused pane rather than exposing a window field: the session list
// belongs to the viewer. Callers still spell it `_active` - see the alias below.
internal Controls.PdfViewer.DocumentSession? ActiveSession => ActiveViewer.ActiveSessionRef;
private Controls.PdfViewer.DocumentSession? _active => ActiveViewer.ActiveSessionRef;
// Reads OUT of the control rather than exposing a window field: the link-rect map belongs
// to the viewer, alongside Links.cs. ContextMenu.cs and FileOperations.cs still call it by
// this name.
private Dictionary<int, List<LinkInfo>> _continuousLinks => ActiveViewer.ContinuousLinks;
// Live gesture state, shared with the annotation and crop tools that have not moved yet.
// ══ OUTWARD: the viewer's members, under the names the window already calls ══════════
// Signatures mirror the originals exactly, defaults included, so no call site changed.
private System.Threading.Tasks.Task RenderContinuousPages(int centerPage) => ActiveViewer.RenderContinuousPages(centerPage);
private void BootstrapDocumentView(int initialPage, bool autoFit, bool restoreFitMode = false)
=> ActiveViewer.BootstrapDocumentView(initialPage, autoFit, restoreFitMode);
private void RefreshPageView(int pageIndex) => ActiveViewer.RefreshPageView(pageIndex);
private void ScrollContinuousToPage(int pageIndex) => ActiveViewer.ScrollContinuousToPage(pageIndex);
private void StartRerenderTimer() => ActiveViewer.StartRerenderTimer();
private void SetZoom(double level) => ActiveViewer.SetZoom(level);
private void SetTrueZoom(double trueZoom) => ActiveViewer.SetTrueZoom(trueZoom);
private void GridZoomStep(bool zoomOut) => ActiveViewer.GridZoomStep(zoomOut);
private double GridZoomForN(int n) => ActiveViewer.GridZoomForN(n);
private double DisplayZoomPct() => ActiveViewer.DisplayZoomPct();
private void SyncZoomBox() => ActiveViewer.SyncZoomBox();
private void FitToWidth(bool lite = false) => ActiveViewer.FitToWidth(lite);
private void FitToPage(bool lite = false) => ActiveViewer.FitToPage(lite);
private void SetViewMode(ViewMode mode) => ActiveViewer.SetViewMode(mode);
private void SelectViewMode(ViewMode mode) => ActiveViewer.SelectViewMode(mode);
private void ApplyViewMode(ViewMode mode) => ActiveViewer.ApplyViewMode(mode);
private ViewMode? _pendingViewMode { get => ActiveViewer.PendingViewMode; set => ActiveViewer.PendingViewMode = value; }
private bool NavigatePageStep(int direction) => ActiveViewer.NavigatePageStep(direction);
private void NavigatePageByWheel(int delta) => ActiveViewer.NavigatePageByWheel(delta);
private int _gridColumns { get => ActiveViewer.GridColumns; set => ActiveViewer.GridColumns = value; }
private void BuildPrimaryTile() => ActiveViewer.BuildPrimaryTile();
private void PagePreviewPanel_SizeChanged(object sender, SizeChangedEventArgs e)
=> ActiveViewer.PagePreviewPanel_SizeChanged(sender, e);
// Bound from MainWindow.xaml (the zoom toolbar stays on the window) and from
// ContextMenu.cs, so these three cannot simply live on the control.
private void ZoomIn_Click(object sender, RoutedEventArgs e) => ActiveViewer.ZoomIn_Click(sender, e);
private void ZoomOut_Click(object sender, RoutedEventArgs e) => ActiveViewer.ZoomOut_Click(sender, e);
// ACTIVEVIEWER, like every other stub in this file - this one said `Viewer` (pane A,
// hardcoded) and was the split's cross-zoom bug: FocusPane(B) -> SyncZoomBox writes the
// shared box -> SelectionChanged -> this stub ran PANE A's handler, which FitToWidth'd
// pane A against pane B's document (proven by zoomtrace, 2026-08-01).
// NULL-CONDITIONAL, and it must stay that way. ZoomBox declares
// <ComboBoxItem Tag="1.0" IsSelected="True"> (MainWindow.xaml), so SelectionChanged fires
// while InitializeComponent is still walking the tree - ActiveViewer is not assigned until
// InitSplitPanes runs after it, so the `?.` no-ops the mid-parse fire exactly like the old
// `_zoomBox?.SelectedItem` guard did. Click handlers do not need this - a click cannot
// happen mid-parse.
private void ZoomBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> ActiveViewer?.ZoomBox_SelectionChanged(sender, e);
// Wheel over the zoom dropdown nudges the zoom like Ctrl+scroll. Null-conditional for the
// same mid-parse reason as SelectionChanged above.
private void ZoomBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
=> ActiveViewer?.ZoomBoxWheel(e);
}
}
@@ -0,0 +1,37 @@
using System.Windows.Controls;
using KillerPDF.Controls;
namespace KillerPDF
{
// The element names MainWindow.xaml used to generate, now that the document pane is a control
// (Controls/PdfViewer.xaml).
//
// Same trick as the ViewerState forwarding: the names stay, the storage moves. Every
// existing call site - DocPaneBorder.CornerRadius in Tabs.cs, DocPaneShadow.Visibility in
// FullScreen.cs, PagePreviewPanel and MarqueeLayer across the render and annotation code -
// compiles untouched.
//
// Get-only is correct here: callers mutate the ELEMENT (its margin, radius, visibility), never
// rebind the reference.
//
// NOTE the one thing that could NOT be forwarded: the card's MARGIN. It lives on the control
// now, not on PaneBorder, because the control is what the layout positions. ApplySidebarSide
// and ApplyFullScreen set ActiveViewer.Margin directly.
public partial class MainWindow
{
private Border DocPaneShadow => ActiveViewer.PaneShadowBorder;
private Border DocPaneBorder => ActiveViewer.PaneCardBorder;
private Grid DocPaneContent => ActiveViewer.ContentHost;
// 7 bare uses in Tabs.cs and Viewport.cs. Note this is a SEPARATE member from the
// underscore-prefixed _pageContentGrid, which forwards to ViewerState - the code uses both
// spellings, so both have to resolve.
private Grid PageContentGrid => ActiveViewer.PageGrid;
private ScrollViewer PagePreviewPanel => ActiveViewer.PreviewScroller;
private Border DropZone => ActiveViewer.DropSurface;
private Border RecentFilesBox => ActiveViewer.RecentBox;
private ItemsControl RecentFilesList => ActiveViewer.RecentList;
private Canvas MarqueeLayer => ActiveViewer.Marquee;
private Border DocSurfacePad => ActiveViewer.SurfacePad;
private System.Windows.Media.ImageBrush GrainBrush => ActiveViewer.Grain;
}
}
+241
View File
@@ -0,0 +1,241 @@
using KillerPDF.Features;
using KillerPDF.Controls;
using System.Windows;
using System.Windows.Input;
using System;
using System.Linq;
namespace KillerPDF
{
// 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();
}
}
}
}
+122
View File
@@ -0,0 +1,122 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using PdfSharpCore.Pdf;
namespace KillerPDF
{
/// <summary>
/// Outward stubs: the annotation, text, crop, form and link members under the names the rest of
/// the window already calls them by.
///
/// The point of this file is that ~170 call sites across ContextMenu.cs, TextSettingsBar.cs,
/// KeyboardShortcuts.cs, FileOperations.cs, Tabs.cs, Signing.cs, Shapes.cs, Search.cs,
/// SidebarOutline.cs, Stamps.cs, ToolSelection.cs, TempReload.cs, Rotate.cs, Ocr.cs and
/// DirtyTracking.cs need no changes at all.
///
/// THE XAML ONES ARE NOT OPTIONAL. WPF resolves Click="Undo_Click" against the code-behind of
/// the XAML ROOT - MainWindow - not against whichever class the method ended up in. Eleven
/// handlers in MainWindow.xaml point at members that now live in the viewer, and without these
/// declarations InitializeComponent throws XamlParseException before the window ever appears.
/// Verified against MainWindow.xaml rather than remembered: Undo_Click (2 bindings),
/// ClearAllAnnotations_Click (2), PageJumpBox_KeyDown, PageJumpBox_GotFocus,
/// PageList_SelectionChanged, ShortcutHelp_Click, ShortcutOverlay_MouseLeftButtonDown (2),
/// ShortcutOverlayCard_MouseLeftButtonDown (2), ShortcutOverlayClose_Click,
/// Hyperlink_RequestNavigate.
/// </summary>
public partial class MainWindow
{
// ── Annotations ──────────────────────────────────────────────────────────────────────
private void RenderAllAnnotations(int pageIndex) => ActiveViewer.RenderAllAnnotations(pageIndex);
private void ClearSelection() => ActiveViewer.ClearSelection();
private void ClearTextSelection() => ActiveViewer.ClearTextSelection();
private SolidColorBrush AccentBrush(byte alpha = 255) => ActiveViewer.AccentBrush(alpha);
private void AddAnnotation(PageAnnotation a) => ActiveViewer.AddAnnotationExt(a);
private Rect AnnotBounds(PageAnnotation a) => ActiveViewer.AnnotBoundsExt(a);
private static Point AnnotGetPos(PageAnnotation a) => Controls.PdfViewer.AnnotGetPosExt(a);
private static void AnnotSetPos(PageAnnotation a, Point pos) => Controls.PdfViewer.AnnotSetPosExt(a, pos);
private Point ClampAnnotPos(PageAnnotation a) => ActiveViewer.ClampAnnotPosExt(a);
private bool HitTestAnnotation(PageAnnotation a, Point pos, out Rect bounds)
=> ActiveViewer.HitTestAnnotationExt(a, pos, out bounds);
private static bool IsDraggable(PageAnnotation a) => Controls.PdfViewer.IsDraggableExt(a);
private void SelectAnnotation(PageAnnotation a, Rect bounds) => ActiveViewer.SelectAnnotationExt(a, bounds);
private void ToggleMultiSelect(PageAnnotation a, Rect bounds, Canvas canvas)
=> ActiveViewer.ToggleMultiSelectExt(a, bounds, canvas);
private void SelectGroup(PageAnnotation lead) => ActiveViewer.SelectGroupExt(lead);
private PageAnnotation? SelectedPaired() => ActiveViewer.SelectedPairedExt();
private int SelectionCount() => ActiveViewer.SelectionCountExt();
private void ReattachSelectionVisuals() => ActiveViewer.ReattachSelectionVisualsExt();
private void UnpairSelected() => ActiveViewer.UnpairSelectedExt();
private void GroupSelected() => ActiveViewer.GroupSelectedExt();
private void UngroupAnnotation(PageAnnotation a) => ActiveViewer.UngroupAnnotationExt(a);
private void RemoveFromGroup(PageAnnotation a) => ActiveViewer.RemoveFromGroupExt(a);
private void DeleteSelected() => ActiveViewer.DeleteSelectedExt();
private bool SelectAllAnnotations() => ActiveViewer.SelectAllAnnotationsExt();
private void HideBrushPreview() => ActiveViewer.HideBrushPreviewExt();
private void FinishStuckGesture() => ActiveViewer.FinishStuckGestureExt();
private void RefreshSelectionAccent() => ActiveViewer.RefreshSelectionAccentExt();
// ── Page canvases ────────────────────────────────────────────────────────────────────
private Canvas CanvasForPage(int page) => ActiveViewer.CanvasForPageExt(page);
private Canvas? VisibleCanvasForPage(int page) => ActiveViewer.VisibleCanvasForPageExt(page);
private IEnumerable<Canvas> AllPageCanvases() => ActiveViewer.AllPageCanvasesExt();
// ── Undo ─────────────────────────────────────────────────────────────────────────────
private void PushDocUndo() => ActiveViewer.PushDocUndoExt();
private void PushPageSnapshotUndo(int pageIdx) => ActiveViewer.PushPageSnapshotUndoExt(pageIdx);
// ── Text editing ─────────────────────────────────────────────────────────────────────
private void CommitActiveTextBox() => ActiveViewer.CommitActiveTextBoxExt();
private void RemoveTextEditHandles() => ActiveViewer.RemoveTextEditHandlesExt();
private void EditTextAtPosition(Point canvasPos, int pageIdx) => ActiveViewer.EditTextAtPositionExt(canvasPos, pageIdx);
private void PlaceTextBox(Point pos, int pageIdx) => ActiveViewer.PlaceTextBoxExt(pos, pageIdx);
private Brush TextEditBackground() => ActiveViewer.TextEditBackgroundExt();
private static ControlTemplate FlatTextBoxTemplate() => Controls.PdfViewer.FlatTextBoxTemplateExt();
// ── Text selection ───────────────────────────────────────────────────────────────────
private void CopySelectedText() => ActiveViewer.CopySelectedTextExt();
private void SelectAllText() => ActiveViewer.SelectAllTextExt();
// ── Crop ─────────────────────────────────────────────────────────────────────────────
private void ApplyCrop(int[] pageIndices) => ActiveViewer.ApplyCropExt(pageIndices);
private void HideCropConfirmBar() => ActiveViewer.HideCropConfirmBarExt();
private void ShowDefaultCropBox() => ActiveViewer.ShowDefaultCropBoxExt();
private void RebuildCropBarForLocale() => ActiveViewer.RebuildCropBarForLocaleExt();
// ── Links ────────────────────────────────────────────────────────────────────────────
private void CloseLinkPdfiumDoc() => ActiveViewer.CloseLinkPdfiumDocExt();
private void AddLinkMenuItems(ContextMenu menu, object target, int annotIndex, int pageIndex)
=> ActiveViewer.AddLinkMenuItemsExt(menu, target, annotIndex, pageIndex);
private int? ResolveDest(PdfItem? destItem) => ActiveViewer.ResolveDestExt(destItem);
private const double LinkHitPad = Controls.PdfViewer.LinkHitPadShared;
internal const string ConfirmLinksSetting = Controls.PdfViewer.ConfirmLinksSetting;
// ── Save paths ───────────────────────────────────────────────────────────────────────
private void DrawAnnotationsOnDocument(int? onlyPage = null) => ActiveViewer.DrawAnnotationsOnDocumentExt(onlyPage);
private void WriteFormValuesToDocument() => ActiveViewer.WriteFormValuesToDocumentExt();
// ── Bound from MainWindow.xaml - see the class comment, these are load-bearing ───────
private void Undo_Click(object sender, RoutedEventArgs e) => ActiveViewer.UndoClickExt(sender, e);
private void Redo_Click(object sender, RoutedEventArgs e) => ActiveViewer.RedoClickExt(sender, e);
private void ClearAnnotations_Click(object sender, RoutedEventArgs e) => ActiveViewer.ClearAnnotationsClickExt(sender, e);
private void ClearAllAnnotations_Click(object sender, RoutedEventArgs e) => ActiveViewer.ClearAllAnnotationsClickExt(sender, e);
private void PageJumpBox_KeyDown(object sender, KeyEventArgs e) => ActiveViewer.PageJumpBoxKeyDownExt(sender, e);
private void PageJumpBox_GotFocus(object sender, RoutedEventArgs e) => ActiveViewer.PageJumpBoxGotFocusExt(sender, e);
private void PageList_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> ActiveViewer.PageListSelectionChangedExt(sender, e);
private void ShortcutHelp_Click(object sender, RoutedEventArgs e) => ActiveViewer.ShortcutHelpClickExt(sender, e);
private void ShortcutOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
=> FadeOverlayOut(ShortcutOverlay);
private void ShortcutOverlayCard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
=> e.Handled = true;
private void ShortcutOverlayClose_Click(object sender, RoutedEventArgs e)
{
e.Handled = true;
FadeOverlayOut(ShortcutOverlay);
}
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
=> ActiveViewer.HyperlinkRequestNavigateExt(sender, e);
}
}