185 lines
8.9 KiB
C#
185 lines
8.9 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Shapes;
|
|
using Docnet.Core;
|
|
using Docnet.Core.Models;
|
|
using Microsoft.Win32;
|
|
using PdfSharpCore.Drawing;
|
|
using PdfSharpCore.Pdf;
|
|
using PdfSharpCore.Pdf.IO;
|
|
using KillerPDF.Services;
|
|
using PdfPigDoc = UglyToad.PdfPig.PdfDocument;
|
|
|
|
namespace KillerPDF.Controls
|
|
{
|
|
// Moved from Shell/PageSelection.cs; the namespace and class line are the only changes. Window
|
|
// members spelled bare here resolve through PdfViewer.Bridge.cs.
|
|
public partial class PdfViewer
|
|
{
|
|
// ============================================================
|
|
// Page selection handler
|
|
// ============================================================
|
|
|
|
private void PageList_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
|
{
|
|
// Same speed knob as the document viewport (WheelScrollFactor in Zoom.cs).
|
|
Host?.ScrollSidebar(this, -e.Delta * (48.0 / 120.0) * Controls.PdfViewer.WheelScrollFactor);
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void PageJumpBox_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key != Key.Enter || _doc is null) return;
|
|
e.Handled = true;
|
|
if (int.TryParse(Host?.PageJumpText, out int pg))
|
|
{
|
|
int idx = Math.Max(0, Math.Min(_doc.PageCount - 1, pg - 1));
|
|
RecordNavJump(); // Alt+Left retraces the typed jump
|
|
_currentPage = idx;
|
|
}
|
|
else
|
|
{
|
|
// Restore current page number if input was invalid
|
|
if (Host != null) Host.PageJumpText = (_currentPage + 1).ToString();
|
|
}
|
|
Keyboard.ClearFocus();
|
|
}
|
|
|
|
private void PageJumpBox_GotFocus(object sender, RoutedEventArgs e)
|
|
{
|
|
Host?.SelectAllPageJumpText();
|
|
}
|
|
|
|
/// <summary>Set by SyncCurrentPageTo (and only it) around its programmatic SelectedIndex
|
|
/// write, so the scroll-driven sync does not re-enter the render path through the window's
|
|
/// XAML-bound handler. Replaces the old detach/attach of a cached delegate, which never
|
|
/// worked: the list's real subscription is the WINDOW stub, so the -= removed nothing and
|
|
/// the += accumulated direct subscriptions (2026-08-01).</summary>
|
|
private bool _syncingPageList;
|
|
|
|
private void PageList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_syncingPageList) return; // programmatic sync, not a user selection
|
|
// The sidebar page list is WINDOW chrome - there is one of it, and BOTH panes attach
|
|
// their own handler to it. It describes the focused pane only, so the other pane has
|
|
// to sit this out. Scrolling a pane calls SyncCurrentPageTo, which detaches its OWN
|
|
// handler before setting SelectedIndex to avoid re-entering the render path; the other
|
|
// pane's handler stayed attached and navigated that pane to the page you had just
|
|
// scrolled to in this one, which is why scrolling pane A scrolled pane B.
|
|
if (Host != null && !Host.IsViewerFocused(this)) return;
|
|
|
|
// Mirror the sidebar into the view's own current page. Set
|
|
// BEFORE the >= 0 guard on purpose - clearing the list (tab close, document close)
|
|
// drops SelectedIndex to -1 and that has to be mirrored too, or a closed document
|
|
// leaves a stale page number behind. Assigns _view.CurrentPage directly rather than
|
|
// going through _currentPage, whose setter would write back into PageList and re-enter
|
|
// this handler.
|
|
State.CurrentPage = (sender as ListBox)?.SelectedIndex ?? -1;
|
|
|
|
if (_currentPage >= 0)
|
|
{
|
|
CommitActiveTextBox();
|
|
ClearSelection();
|
|
ClearTextSelection();
|
|
Host?.EnsureSidebarPageVisible(this, _currentPage);
|
|
if (_viewMode == ViewMode.Continuous)
|
|
{
|
|
if (Host != null) Host.PageJumpText = (_currentPage + 1).ToString();
|
|
ScrollContinuousToPage(_currentPage);
|
|
return;
|
|
}
|
|
if (_viewMode == ViewMode.Grid)
|
|
{
|
|
// Grid is a stable overview: selecting a page highlights it but must NOT
|
|
// re-anchor the grid. It still needs an initial render (open / first display)
|
|
// when no tiles exist yet; later selections only update the highlight.
|
|
if (Host != null) Host.PageJumpText = (_currentPage + 1).ToString();
|
|
// Keep the statusbar counter honest even when the clicked tile is already in
|
|
// view (BringIntoView then scrolls nothing, so the scroll-sync never fires).
|
|
SetStatus(string.Format(Loc("Str_PageOf"), _currentPage + 1, _doc!.PageCount) + $" - {DisplayZoomPct():F0}%");
|
|
if (_pageContentPanel.Children.Count <= 1)
|
|
{
|
|
PagePreviewPanel.ScrollToTop();
|
|
PagePreviewPanel.ScrollToHorizontalOffset(0);
|
|
RenderPage(0); // grid primary is always page 0
|
|
// Default the grid to a clean 3-columns-across fit. Deferred to Loaded so the
|
|
// viewport width is valid (it can still be 0 mid-open, which would fall back
|
|
// to a carried-over zoom and show a single large page).
|
|
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded,
|
|
(Action)(() => SetZoom(GridZoomForN(Math.Min(_doc?.PageCount ?? 1, 3)))));
|
|
}
|
|
else if (_currentPage < _pageContentPanel.Children.Count
|
|
&& _pageContentPanel.Children[_currentPage] is FrameworkElement gridTile)
|
|
{
|
|
// Scroll the chosen page's tile into view (BringIntoView accounts for the zoom transform).
|
|
gridTile.BringIntoView();
|
|
}
|
|
return;
|
|
}
|
|
// Clicking either page of the spread that's already shown (or re-selecting the
|
|
// current single page) renders the exact same pixels, so skip the re-render and its
|
|
// flash - just move the page number. SpreadStart, NOT a local % 2: this was the
|
|
// fourth pairing site and the one #193's book layout missed - its stale (0,1)
|
|
// pairing matched the rendered cover and swallowed the render of spread (1,2).
|
|
int targetPrimary = _currentPage;
|
|
if (_viewMode == ViewMode.TwoPage) targetPrimary = SpreadStart(targetPrimary);
|
|
if (targetPrimary == _renderedPrimaryPage && Math.Abs(_zoomLevel - _lastRenderZoom) < 0.0001)
|
|
{
|
|
if (Host != null) Host.PageJumpText = (_currentPage + 1).ToString();
|
|
return;
|
|
}
|
|
PagePreviewPanel.ScrollToTop();
|
|
PagePreviewPanel.ScrollToHorizontalOffset(0);
|
|
RenderPage(_currentPage);
|
|
ApplyZoom();
|
|
// Update page jump box
|
|
if (Host != null) Host.PageJumpText = (_currentPage + 1).ToString();
|
|
// Re-highlight search results on this page if a search is active
|
|
if (_searchBar is not null && _searchBar.Visibility == Visibility.Visible
|
|
&& Search.HasResults)
|
|
HighlightSearchResultsOnCurrentPage();
|
|
}
|
|
}
|
|
|
|
private void ShortcutHelp_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (ShortcutOverlay.Visibility == Visibility.Visible) FadeOverlayOut(ShortcutOverlay);
|
|
else ShowShortcutsOverlayExclusive();
|
|
}
|
|
|
|
private void ShortcutOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// Click on the dim backdrop closes the overlay.
|
|
FadeOverlayOut(ShortcutOverlay);
|
|
}
|
|
|
|
private void ShortcutOverlayCard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// Stop the click from bubbling up to the backdrop handler.
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void ShortcutOverlayClose_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
FadeOverlayOut(ShortcutOverlay);
|
|
}
|
|
|
|
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
|
|
{
|
|
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
|
|
e.Handled = true;
|
|
}
|
|
|
|
}
|
|
|
|
}
|