PDFKitViewer¶
A PyObjC Example without documentation
Sources¶
AppDelegate.py¶
from Cocoa import NSObject
class AppDelegate(NSObject):
def applicationShouldOpenUntitledFile_(self, application):
return False
MyPDFDocument.py¶
import Cocoa
import objc
import Quartz
from objc import super # noqa: A004
class MyPDFDocument(Cocoa.NSDocument):
_outline = objc.ivar()
_searchResults = objc.ivar()
_drawer = objc.IBOutlet()
_noOutlineText = objc.IBOutlet()
_outlineView = objc.IBOutlet()
_pdfView = objc.IBOutlet()
_searchProgress = objc.IBOutlet()
_searchTable = objc.IBOutlet()
def dealloc(self):
Cocoa.NSNotificationCenter.defaultCenter().removeObserver_(self)
self._searchResults = None
super().dealloc()
def windowNibName(self):
return "MyDocument"
def windowControllerDidLoadNib_(self, controller):
super().windowControllerDidLoadNib_(controller)
if self.fileName():
pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(
Cocoa.NSURL.fileURLWithPath_(self.fileName())
)
self._pdfView.setDocument_(pdfDoc)
# Page changed notification.
Cocoa.NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
self, "pageChanged:", Quartz.PDFViewPageChangedNotification, self._pdfView
)
# Find notifications.
center = Cocoa.NSNotificationCenter.defaultCenter()
center.addObserver_selector_name_object_(
self,
"startFind:",
Quartz.PDFDocumentDidBeginFindNotification,
self._pdfView.document(),
)
center.addObserver_selector_name_object_(
self,
"findProgress:",
Quartz.PDFDocumentDidEndPageFindNotification,
self._pdfView.document(),
)
center.addObserver_selector_name_object_(
self,
"endFind:",
Quartz.PDFDocumentDidEndFindNotification,
self._pdfView.document(),
)
# Set self to be delegate (find).
self._pdfView.document().setDelegate_(self)
# Get outline.
self._outline = self._pdfView.document().outlineRoot()
if self._outline is not None:
# Remove text that says, "No outline."
self._noOutlineText.removeFromSuperview()
self._noOutlineText = None
# Force it to load up.
self._outlineView.reloadData()
else:
# Remove outline view (leaving instead text that says,
# "No outline.").
self._outlineView.enclosingScrollView().removeFromSuperview()
self._outlineView = None
# Open drawer.
self._drawer.open()
# Size the window.
windowSize = self._pdfView.rowSizeForPage_(self._pdfView.currentPage())
if (self._pdfView.displayMode() & 0x01) and (
self._pdfView.document().pageCount() > 1
):
windowSize.width += Cocoa.NSScroller.scrollerWidth()
controller.window().setContentSize_(windowSize)
def dataRepresentationOfType_(self, aType):
return None
def loadDataRepresentation_ofType_(self, data, aType):
return True
@objc.IBAction
def toggleDrawer_(self, sender):
self._drawer.toggle_(self)
@objc.IBAction
def takeDestinationFromOutline_(self, sender):
# Get the destination associated with the search result list.
# Tell the PDFView to go there.
self._pdfView.goToDestination_(
sender.itemAtRow_(sender.selectedRow()).destination()
)
@objc.IBAction
def displaySinglePage_(self, sender):
# Display single page mode.
if self._pdfView.displayMode() > Quartz.kPDFDisplaySinglePageContinuous:
self._pdfView.setDisplayMode_(self._pdfView.displayMode() - 2)
@objc.IBAction
def displayTwoUp_(self, sender):
# Display two-up.
if self._pdfView.displayMode() < Quartz.kPDFDisplayTwoUp:
self._pdfView.setDisplayMode_(self._pdfView.displayMode() + 2)
def pageChanged_(self, notification):
# Skip out if there is no outline.
if self.pdfView.document().outlineRoot() is None:
return
# What is the new page number (zero-based).
newPageIndex = self._pdfView.document().indexForPage_(
self._pdfView.currentPage()
)
# Walk outline view looking for best firstpage number match.
newlySelectedRow = -1
numRows = self._outlineView.numberOfRows()
for i in range(numRows):
outlineItem = self._outlineView.itemAtRow_(i)
if (
self._pdfView.document().indexForPage_(outlineItem.destination().page())
== newPageIndex
):
newlySelectedRow = i
self._outlineView.selectRow_byExtendingSelection_(
newlySelectedRow, False
)
break
elif (
self._pdfView.document().indexForPage_(outlineItem.destination().page())
> newPageIndex
):
newlySelectedRow = i - 1
self._outlineView.selectRow_byExtendingSelection_(
newlySelectedRow, False
)
break
# Auto-scroll.
if newlySelectedRow != -1:
self._outlineView.scrollRowToVisible_(newlySelectedRow)
def doFind_(self, sender):
if self._pdfView.document().isFinding():
self._pdfView.document().cancelFindString()
# Lazily allocate _searchResults.
if self._searchResults is None:
self._searchResults = Cocoa.NSMutableArray.arrayWithCapacity_(10)
self._pdfView.document().beginFindString_withOptions_(
sender.stringValue(), Cocoa.NSCaseInsensitiveSearch
)
def startFind_(self, notification):
# Empty arrays.
self._searchResults.removeAllObjects()
self._searchTable.reloadData()
self._searchProgress.startAnimation_(self)
def findProgress_(self, notification):
pageIndex = notification.userInfo().objectForKey_(
"PDFDocumentPageIndex"
) # .doubleValue()
self._searchProgress.setDoubleValue_(
pageIndex / self._pdfView.document().pageCount()
)
def didMatchString_(self, instance):
# Add page label to our array.
self._searchResults.addObject_(instance.copy())
self._searchTable.reloadData()
def endFind_(self, notification):
self._searchProgress.stopAnimation_(self)
self._searchProgress.setDoubleValue_(0)
# The table view is used to hold search results. Column 1 lists the
# page number for the search result, column two the section in the PDF
# (x-ref with the PDF outline) where the result appears.
def numberOfRowsInTableView_(self, aTableView):
if self._searchResults is None:
return 0
return self._searchResults.count()
def tableView_objectValueForTableColumn_row_(self, aTableView, theColumn, rowIndex):
if theColumn.identifier() == "page":
return (
self._searchResults.objectAtIndex_(rowIndex)
.pages()
.objectAtIndex_(0)
.label()
)
elif theColumn.identifier() == "section":
value = self._pdfView.document().outlineItemForSelection_(
self._searchResults.objectAtIndex_(rowIndex)
)
if value is None:
return None
return value.label()
else:
return None
def tableViewSelectionDidChange_(self, notification):
# What was selected. Skip out if the row has not changed.
rowIndex = notification.object().selectedRow()
if rowIndex >= 0:
self._pdfView.setCurrentSelection_(
self._searchResults.objectAtIndex_(rowIndex)
)
self._pdfView.centerSelectionInVisibleArea_(self)
# The outline view is for the PDF outline. Not all PDF's have an outline.
def outlineView_numberOfChildrenOfItem_(self, outlineView, item):
if item is None:
if self._outline is not None:
return self._outline.numberOfChildren()
else:
return 0
else:
return item.numberOfChildren()
def outlineView_child_ofItem_(self, outlineView, index, item):
if item is None:
if self._outline is not None:
return self._outline.childAtIndex_(index).retain()
else:
return None
else:
return item.childAtIndex_(index).retain()
def outlineView_isItemExpandable_(self, outlineView, item):
if item is None:
if self._outline:
return self._outline.numberOfChildren() > 0
else:
return False
else:
return item.numberOfChildren() > 0
def outlineView_objectValueForTableColumn_byItem_(
self, outlineView, tableColumn, item
):
return item.label()
main.py¶
import AppDelegate # noqa: F401
import MyPDFDocument # noqa: F401
from PyObjCTools import AppHelper
AppHelper.runEventLoop()
setup.py¶
"""
Script for building the example.
Usage:
python3 setup.py py2app
"""
from setuptools import setup
setup(
name="PDFKitViewer",
app=["main.py"],
data_files=["English.lproj", "pdfkitviewer.icns"],
options={"py2app": {"plist": "Info.plist"}},
setup_requires=["py2app", "pyobjc-framework-Cocoa", "pyobjc-framework-Quartz"],
)