Mouse scroll wheel! New PDF viewer demo
This commit is contained in:
parent
c482dee57e
commit
240a0a71e4
|
@ -1,79 +1,183 @@
|
||||||
|
"""
|
||||||
|
@created: 2018-08-19 18:00:00
|
||||||
|
|
||||||
|
@author: (c) 2018 Jorj X. McKie
|
||||||
|
|
||||||
|
Display a PyMuPDF Document using Tkinter
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
-------------
|
||||||
|
PyMuPDF, PySimpleGUI > v2.9.0, Tkinter with Tk v8.6+, Python 3
|
||||||
|
|
||||||
|
|
||||||
|
License:
|
||||||
|
--------
|
||||||
|
GNU GPL V3+
|
||||||
|
|
||||||
|
Description
|
||||||
|
------------
|
||||||
|
Read filename from command line and start display with page 1.
|
||||||
|
Pages can be directly jumped to, or buttons for paging can be used.
|
||||||
|
For experimental / demonstration purposes, we have included options to zoom
|
||||||
|
into the four page quadrants (top-left, bottom-right, etc.).
|
||||||
|
|
||||||
|
We also interpret keyboard events to support paging by PageDown / PageUp
|
||||||
|
keys as if the resp. buttons were clicked. Similarly, we do not include
|
||||||
|
a 'Quit' button. Instead, the ESCAPE key can be used, or cancelling the form.
|
||||||
|
|
||||||
|
To improve paging performance, we are not directly creating pixmaps from
|
||||||
|
pages, but instead from the fitz.DisplayList of the page. A display list
|
||||||
|
will be stored in a list and looked up by page number. This way, zooming
|
||||||
|
pixmaps and page re-visits will re-use a once-created display list.
|
||||||
|
|
||||||
|
"""
|
||||||
import sys
|
import sys
|
||||||
import fitz
|
import fitz
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
from binascii import hexlify
|
||||||
|
|
||||||
try:
|
if len(sys.argv) == 1:
|
||||||
|
rc, fname = sg.GetFileBox('PDF Browser', 'PDF file to open', file_types=(("PDF Files", "*.pdf"),))
|
||||||
|
if rc is False:
|
||||||
|
sg.MsgBoxCancel('Cancelling')
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
fname = sys.argv[1]
|
fname = sys.argv[1]
|
||||||
except:
|
|
||||||
fname = 'C:/Python/PycharmProjects/GooeyGUI/test.pdf'
|
|
||||||
doc = fitz.open(fname)
|
doc = fitz.open(fname)
|
||||||
title = "PyMuPDF display of '%s' (%i pages)" % (fname, len(doc))
|
page_count = len(doc)
|
||||||
|
|
||||||
def get_page(pno, zoom = 0):
|
# storage for page display lists
|
||||||
page = doc[pno]
|
dlist_tab = [None] * page_count
|
||||||
r = page.rect
|
|
||||||
mp = r.tl + (r.br - r.tl) * 0.5
|
title = "PyMuPDF display of '%s', pages: %i" % (fname, page_count)
|
||||||
mt = r.tl + (r.tr - r.tl) * 0.5
|
|
||||||
ml = r.tl + (r.bl - r.tl) * 0.5
|
|
||||||
mr = r.tr + (r.br - r.tr) * 0.5
|
def get_page(pno, zoom=0):
|
||||||
mb = r.bl + (r.br - r.bl) * 0.5
|
"""Return a PNG image for a document page number. If zoom is other than 0, one of the 4 page quadrants are zoomed-in instead and the corresponding clip returned.
|
||||||
mat = fitz.Matrix(2, 2)
|
|
||||||
if zoom == 1:
|
"""
|
||||||
|
dlist = dlist_tab[pno] # get display list
|
||||||
|
if not dlist: # create if not yet there
|
||||||
|
dlist_tab[pno] = doc[pno].getDisplayList()
|
||||||
|
dlist = dlist_tab[pno]
|
||||||
|
r = dlist.rect # page rectangle
|
||||||
|
mp = r.tl + (r.br - r.tl) * 0.5 # rect middle point
|
||||||
|
mt = r.tl + (r.tr - r.tl) * 0.5 # middle of top edge
|
||||||
|
ml = r.tl + (r.bl - r.tl) * 0.5 # middle of left edge
|
||||||
|
mr = r.tr + (r.br - r.tr) * 0.5 # middle of right egde
|
||||||
|
mb = r.bl + (r.br - r.bl) * 0.5 # middle of bottom edge
|
||||||
|
mat = fitz.Matrix(2, 2) # zoom matrix
|
||||||
|
if zoom == 1: # top-left quadrant
|
||||||
clip = fitz.Rect(r.tl, mp)
|
clip = fitz.Rect(r.tl, mp)
|
||||||
elif zoom == 4:
|
elif zoom == 4: # bot-right quadrant
|
||||||
clip = fitz.Rect(mp, r.br)
|
clip = fitz.Rect(mp, r.br)
|
||||||
elif zoom == 2:
|
elif zoom == 2: # top-right
|
||||||
clip = fitz.Rect(mt, mr)
|
clip = fitz.Rect(mt, mr)
|
||||||
elif zoom == 3:
|
elif zoom == 3: # bot-left
|
||||||
clip = fitz.Rect(ml, mb)
|
clip = fitz.Rect(ml, mb)
|
||||||
if zoom == 0:
|
if zoom == 0: # total page
|
||||||
pix = page.getPixmap(alpha = False)
|
pix = dlist.getPixmap(alpha=False)
|
||||||
else:
|
else:
|
||||||
pix = page.getPixmap(alpha = False, matrix = mat, clip = clip)
|
pix = dlist.getPixmap(alpha=False, matrix=mat, clip=clip)
|
||||||
return pix.getPNGData()
|
return pix.getPNGData() # return the PNG image
|
||||||
|
|
||||||
form = sg.FlexForm(title, return_keyboard_events=True)
|
|
||||||
|
|
||||||
data = get_page(0)
|
form = sg.FlexForm(title, return_keyboard_events=True, use_default_focus=False)
|
||||||
|
|
||||||
|
cur_page = 0
|
||||||
|
data = get_page(cur_page) # show page 1 for start
|
||||||
image_elem = sg.Image(data=data)
|
image_elem = sg.Image(data=data)
|
||||||
layout = [ [image_elem],
|
goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True)
|
||||||
[sg.ReadFormButton('Next'),
|
|
||||||
sg.ReadFormButton('Prev'),
|
layout = [
|
||||||
sg.ReadFormButton('First'),
|
[
|
||||||
sg.ReadFormButton('Last'),
|
sg.ReadFormButton('Next'),
|
||||||
sg.ReadFormButton('Zoom-1'),
|
sg.ReadFormButton('Prev'),
|
||||||
sg.ReadFormButton('Zoom-2'),
|
sg.Text('Page:'),
|
||||||
sg.ReadFormButton('Zoom-3'),
|
goto,
|
||||||
sg.ReadFormButton('Zoom-4'),
|
],
|
||||||
sg.Quit()] ]
|
[
|
||||||
|
sg.Text("Zoom:"),
|
||||||
|
sg.ReadFormButton('Top-L'),
|
||||||
|
sg.ReadFormButton('Top-R'),
|
||||||
|
sg.ReadFormButton('Bot-L'),
|
||||||
|
sg.ReadFormButton('Bot-R'),
|
||||||
|
],
|
||||||
|
[image_elem],
|
||||||
|
]
|
||||||
|
|
||||||
form.Layout(layout)
|
form.Layout(layout)
|
||||||
|
my_keys = ("Next", "Next:34", "Prev", "Prior:33", "Top-L", "Top-R",
|
||||||
|
"Bot-L", "Bot-R", "MouseWheel:Down", "MouseWheel:Up")
|
||||||
|
zoom_buttons = ("Top-L", "Top-R", "Bot-L", "Bot-R")
|
||||||
|
|
||||||
|
old_page = 0
|
||||||
|
old_zoom = 0 # used for zoom on/off
|
||||||
|
# the zoom buttons work in on/off mode.
|
||||||
|
|
||||||
i = oldzoom = 0
|
|
||||||
while True:
|
while True:
|
||||||
button,value = form.Read()
|
button, value = form.ReadNonBlocking()
|
||||||
zoom = 0
|
zoom = 0
|
||||||
if button in (None, 'Quit'):
|
force_page = False
|
||||||
|
if button is None and value is None:
|
||||||
break
|
break
|
||||||
if button in ("Next", 'Next:34'):
|
if button is None:
|
||||||
i += 1
|
continue
|
||||||
elif button in ("Prev", "Prior:33"):
|
|
||||||
i -= 1
|
if button in ("Escape:27"): # this spares me a 'Quit' button!
|
||||||
elif button == "First":
|
break
|
||||||
i = 0
|
# print("hex(button)", hexlify(button.encode()))
|
||||||
elif button == "Last":
|
if button[0] == chr(13): # surprise: this is 'Enter'!
|
||||||
i = -1
|
try:
|
||||||
elif button == "Zoom-1":
|
cur_page = int(value[0]) - 1 # check if valid
|
||||||
zoom = oldzoom = 0 if oldzoom == 1 else 1
|
while cur_page < 0:
|
||||||
elif button == "Zoom-2":
|
cur_page += page_count
|
||||||
zoom = oldzoom = 0 if oldzoom == 2 else 2
|
except:
|
||||||
elif button == "Zoom-3":
|
cur_page = 0 # this guy's trying to fool me
|
||||||
zoom = oldzoom = 0 if oldzoom == 3 else 3
|
goto.Update(str(cur_page + 1))
|
||||||
elif button == "Zoom-4":
|
# goto.TKStringVar.set(str(cur_page + 1))
|
||||||
zoom = oldzoom = 0 if oldzoom == 4 else 4
|
|
||||||
try:
|
elif button in ("Next", "Next:34", "MouseWheel:Down"):
|
||||||
data = get_page(i, zoom)
|
cur_page += 1
|
||||||
except:
|
elif button in ("Prev", "Prior:33", "MouseWheel:Up"):
|
||||||
i = 0
|
cur_page -= 1
|
||||||
data = get_page(i, zoom)
|
elif button == "Top-L":
|
||||||
image_elem.Update(data=data)
|
zoom = 1
|
||||||
|
elif button == "Top-R":
|
||||||
|
zoom = 2
|
||||||
|
elif button == "Bot-L":
|
||||||
|
zoom = 3
|
||||||
|
elif button == "Bot-R":
|
||||||
|
zoom = 4
|
||||||
|
|
||||||
|
# sanitize page number
|
||||||
|
if cur_page >= page_count: # wrap around
|
||||||
|
cur_page = 0
|
||||||
|
while cur_page < 0: # we show conventional page numbers
|
||||||
|
cur_page += page_count
|
||||||
|
|
||||||
|
# prevent creating same data again
|
||||||
|
if cur_page != old_page:
|
||||||
|
zoom = old_zoom = 0
|
||||||
|
force_page = True
|
||||||
|
|
||||||
|
if button in zoom_buttons:
|
||||||
|
if 0 < zoom == old_zoom:
|
||||||
|
zoom = 0
|
||||||
|
force_page = True
|
||||||
|
|
||||||
|
if zoom != old_zoom:
|
||||||
|
force_page = True
|
||||||
|
|
||||||
|
if force_page:
|
||||||
|
data = get_page(cur_page, zoom)
|
||||||
|
image_elem.Update(data=data)
|
||||||
|
old_page = cur_page
|
||||||
|
old_zoom = zoom
|
||||||
|
|
||||||
|
# update page number field
|
||||||
|
if button in my_keys or not value[0]:
|
||||||
|
goto.Update(str(cur_page + 1))
|
||||||
|
# goto.TKStringVar.set(str(cur_page + 1))
|
||||||
|
|
|
@ -1027,7 +1027,6 @@ class FlexForm:
|
||||||
return BuildResults(self, False, self)
|
return BuildResults(self, False, self)
|
||||||
|
|
||||||
def KeyboardCallback(self, event ):
|
def KeyboardCallback(self, event ):
|
||||||
# print(".",)
|
|
||||||
self.LastButtonClicked = None
|
self.LastButtonClicked = None
|
||||||
self.FormRemainedOpen = True
|
self.FormRemainedOpen = True
|
||||||
if event.char != '':
|
if event.char != '':
|
||||||
|
@ -1038,6 +1037,16 @@ class FlexForm:
|
||||||
results = BuildResults(self, False, self)
|
results = BuildResults(self, False, self)
|
||||||
self.TKroot.quit()
|
self.TKroot.quit()
|
||||||
|
|
||||||
|
def MouseWheelCallback(self, event ):
|
||||||
|
self.LastButtonClicked = None
|
||||||
|
self.FormRemainedOpen = True
|
||||||
|
# print(ObjToStringSingleObj(event))
|
||||||
|
direction = 'Down' if event.delta < 0 else 'Up'
|
||||||
|
self.LastKeyboardEvent = 'MouseWheel:' + direction
|
||||||
|
if not self.NonBlocking:
|
||||||
|
results = BuildResults(self, False, self)
|
||||||
|
self.TKroot.quit()
|
||||||
|
|
||||||
|
|
||||||
def _Close(self):
|
def _Close(self):
|
||||||
try:
|
try:
|
||||||
|
@ -1796,8 +1805,10 @@ def StartupTK(my_flex_form):
|
||||||
my_flex_form.SetIcon(my_flex_form.WindowIcon)
|
my_flex_form.SetIcon(my_flex_form.WindowIcon)
|
||||||
if my_flex_form.ReturnKeyboardEvents and not my_flex_form.NonBlocking:
|
if my_flex_form.ReturnKeyboardEvents and not my_flex_form.NonBlocking:
|
||||||
root.bind("<KeyRelease>", my_flex_form.KeyboardCallback)
|
root.bind("<KeyRelease>", my_flex_form.KeyboardCallback)
|
||||||
|
root.bind("<MouseWheel>", my_flex_form.MouseWheelCallback)
|
||||||
elif my_flex_form.ReturnKeyboardEvents:
|
elif my_flex_form.ReturnKeyboardEvents:
|
||||||
root.bind("<Key>", my_flex_form.KeyboardCallback)
|
root.bind("<Key>", my_flex_form.KeyboardCallback)
|
||||||
|
root.bind("<MouseWheel>", my_flex_form.MouseWheelCallback)
|
||||||
|
|
||||||
if my_flex_form.AutoClose:
|
if my_flex_form.AutoClose:
|
||||||
duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration
|
duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration
|
||||||
|
|
Loading…
Reference in New Issue