Skip to content
This repository has been archived by the owner on Dec 22, 2023. It is now read-only.

Commit

Permalink
Let wx.ScrolledView handle vertical scrolling, too
Browse files Browse the repository at this point in the history
This makes our panel render the full document (rather than just the part which is currently visible due to the scroll position), and then let wx.ScrollView scroll through that fully-rendered view.

This is still incomplete, as it lacks
- porting the remaining scrolling functionality from our panel to wx.ScrolledView (e. g. `makeLineVisible`, which should automatically scroll to the current line)
- port all views over to render the full document rather than only the currently visible parts, and provide information about size and line height

It also has the draw-back that it always renders the full document, which might lead to performance problems with really large documents and/or older computers (not tested yet and no issues know, but theoretically, it increases the use of resources).
  • Loading branch information
janopae committed Oct 1, 2023
1 parent bee7537 commit 650c749
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 48 deletions.
13 changes: 8 additions & 5 deletions src/trelby.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,23 +163,22 @@ def __init__(self, parent, id):
self, parent, id,
# wxMSW/Windows does not seem to support
# wx.NO_BORDER, which sucks
style = wx.WANTS_CHARS | wx.NO_BORDER | wx.HSCROLL)
style = wx.WANTS_CHARS | wx.NO_BORDER | wx.HSCROLL | wx.VSCROLL)

hsizer = wx.BoxSizer(wx.HORIZONTAL)

self.scrollBarVertical = wx.ScrollBar(self, -1, style = wx.SB_VERTICAL)
self.ctrl = MyCtrl(self, -1)

hsizer.Add(self.ctrl, 1, wx.EXPAND)
hsizer.Add(self.scrollBarVertical, 0, wx.EXPAND)

self.scrollBarVertical.Bind(wx.EVT_COMMAND_SCROLL, self.ctrl.OnScroll)

self.scrollBarVertical.Bind(wx.EVT_SET_FOCUS, self.OnScrollbarFocus)

self.SetSizer(hsizer)
self.SetScrollRate(int(self.ctrl.chX), int(self.ctrl.chY))
self.EnableScrolling(True, False)
self.EnableScrolling(True, True)

# we never want the scrollbar to get the keyboard focus, pass it on to
# the main widget
Expand All @@ -188,7 +187,7 @@ def OnScrollbarFocus(self, event):

class MyCtrl(wx.Control):

def __init__(self, parent, id):
def __init__(self, parent: MyPanel, id):
style = wx.WANTS_CHARS | wx.FULL_REPAINT_ON_RESIZE | wx.NO_BORDER
wx.Control.__init__(self, parent, id, style = style)

Expand Down Expand Up @@ -435,9 +434,13 @@ def isUntouched(self):
else:
return True

def getVisibleAreaSize(self):
''' Size of the area visble in the scrolled window'''
return self.panel.GetSize()

def updateScreen(self, redraw = True, setCommon = True):
self.adjustScrollBar()
self.SetMinSize(wx.Size(int(self.pageW), 10)) # the vertical min size is irrelevant currently, as vertical scrolling is still self-implemented
self.SetMinSize(wx.Size(int(self.pageW), gd.vm.getDocumentHeight(self)))
self.PostSizeEventToParent()

if setCommon:
Expand Down
131 changes: 88 additions & 43 deletions src/viewmode.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# -*- coding: iso-8859-1 -*-
import math
from typing import Optional

import config
import mypager
import pml
import screenplay
import trelby
import util

# Number of lines the smooth scroll will try to search. 15-20 is a good
Expand Down Expand Up @@ -82,6 +86,9 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
def getLineHeight(self, ctrl):
raise Exception("getLineHeight not implemented")

def getDocumentHeight(self, ctrl, untilLine: Optional[int] = None) -> int:
raise Exception("getLineHeight not implemented")

# return width of one page in (floating point) pixels
def getPageWidth(self, ctrl):
raise Exception("getPageWidth not implemented")
Expand Down Expand Up @@ -147,6 +154,10 @@ def makeLineVisibleGeneric(self, ctrl, line, texts, direction, jumpAhead):

# helper function for makeLineVisibleGeneric
def _makeLineVisibleHelper(self, ctrl, line, direction, jumpAhead):
currentLine = ctrl.sp.line
linePosition = self.getDocumentHeight(ctrl, currentLine)


startLine = ctrl.sp.getTopLine()
sign = 1 if (direction == config.SCROLL_DOWN) else -1
i = 1
Expand Down Expand Up @@ -207,13 +218,13 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):

marginLeft = int(ctrl.mm2p * cfg.marginLeft)
cox = util.clamp((width - ctrl.pageW) // 2, 0)
fyd = ctrl.sp.cfgGl.fontYdelta
fyd = self.getLineHeight(ctrl)
length = len(ls)

texts = []

while (y < height) and (i < length):
y += int((ctrl.sp.getSpacingBefore(i) / 10.0) * fyd)
y += self.__getLineHeightWithSpacing(ctrl, i)

if y >= height:
break
Expand Down Expand Up @@ -245,6 +256,22 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
def getLineHeight(self, ctrl):
return ctrl.sp.cfgGl.fontYdelta

def __getLineHeightWithSpacing(self, ctrl, lineNumber: int):
lineHeightWithoutSpacing = self.getLineHeight(ctrl)
return lineHeightWithoutSpacing + int((ctrl.sp.getSpacingBefore(lineNumber) / 10.0) * lineHeightWithoutSpacing)

def getDocumentHeight(self, ctrl, untilLine: Optional[int] = None) -> int:
if untilLine is None:
untilLine = len(ctrl.sp.lines)
height = 0
for i in range(untilLine):
height += self.__getLineHeightWithSpacing(ctrl, i)

height += ctrl.getVisibleAreaSize().GetHeight() # One should always be able to scroll at least until the last line is on top

return height


def getPageWidth(self, ctrl):
# this is not really used for much in draft mode, as it has no
# concept of page width, but it's safer to return something
Expand All @@ -264,7 +291,7 @@ def pageCmd(self, ctrl, cs, dir, texts, dpages):
# Layout view mode. Pages are shown with the actual layout they would
# have.
class ViewModeLayout(ViewMode):

PAGE_GAP = 10 # gap between pages (pixels)
def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
cfgGui = ctrl.getCfgGui()
textOp = pml.TextOp
Expand All @@ -274,8 +301,6 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):

width, height = ctrl.GetClientSize()

# gap between pages (pixels)
pageGap = 10
pager = mypager.Pager(ctrl.sp.cfg)

mm2p = ctrl.mm2p
Expand Down Expand Up @@ -307,7 +332,7 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
if not topOfPage:
y = -int(op.y * mm2p)
else:
y = pageGap
y = self.PAGE_GAP

break
else:
Expand Down Expand Up @@ -366,7 +391,7 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
cfgGui.fonts[op.flags & 3],
op.flags & pml.UNDERLINED))

y = pageY + ctrl.pageH + pageGap
y = pageY + ctrl.pageH + self.PAGE_GAP
pg = None

# if user has inserted new text causing the script to overflow
Expand All @@ -385,6 +410,23 @@ def getLineHeight(self, ctrl):
# lines.
return int(ctrl.chY * ctrl.mm2p + 1.0)

def getDocumentHeight(self, ctrl, untilLine: Optional[int] = None) -> int:
'''
:type ctrl: trelby.MyCtrl
'''
if untilLine is None:
untilPage = len(ctrl.sp.pages)
else:
untilPage = ctrl.sp.line2page(untilLine) - 1

height = (untilPage - 1) * (ctrl.pageH + self.PAGE_GAP)
height += 2 * self.PAGE_GAP # gap on top and on the bottom

if untilLine is not None:
height += ctrl.chY * ctrl.mm2p # in case a specific line is requested, we need to be line exact

return height

def getPageWidth(self, ctrl):
return (ctrl.sp.cfg.paperWidth / ctrl.chX) *\
ctrl.getCfgGui().fonts[pml.NORMAL].fx
Expand All @@ -402,7 +444,7 @@ def pageCmd(self, ctrl, cs, dir, texts, dpages):
# would have, as many pages at a time as fit on the screen, complete pages
# only, in a single row.
class ViewModeSideBySide(ViewMode):

PAGE_GAP = 10 # gap between pages (+ screen left edge)
def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):
cfgGui = ctrl.getCfgGui()
textOp = pml.TextOp
Expand All @@ -414,64 +456,67 @@ def getScreen(self, ctrl, doExtra, partials = False, pageCache = None):

mm2p = ctrl.mm2p

# gap between pages (+ screen left edge)
pageGap = 10

# how many pages fit on screen
pageCnt = max(1, (width - pageGap) // (ctrl.pageW + pageGap))
pagesPerRow = max(1, (width - self.PAGE_GAP) // (ctrl.pageW + self.PAGE_GAP))

pager = mypager.Pager(ctrl.sp.cfg)

topLine = ctrl.sp.getTopLine()
pageNr = ctrl.sp.line2page(topLine)
pageNr = 1 # for now, we just render the whole document

if doExtra and ctrl.sp.cfg.pdfShowSceneNumbers:
pager.scene = ctrl.sp.getSceneNumber(
ctrl.sp.page2lines(pageNr)[0] - 1)

pagesDone = 0

while 1:
if (pagesDone >= pageCnt) or (pageNr >= len(ctrl.sp.pages)):
break
sy = self.PAGE_GAP
while (pageNr < len(ctrl.sp.pages)):
pagesDonePerRow = 0
sx = self.PAGE_GAP
while 1:
if (pagesDonePerRow >= pagesPerRow) or (pageNr >= len(ctrl.sp.pages)):
break

# we'd have to go back an arbitrary number of pages to get an
# accurate number for this in the worst case, so disable it
# altogether.
pager.sceneContNr = 0
# we'd have to go back an arbitrary number of pages to get an
# accurate number for this in the worst case, so disable it
# altogether.
pager.sceneContNr = 0

if pageCache:
pg = pageCache.getPage(pager, pageNr)
else:
pg = ctrl.sp.generatePMLPage(pager, pageNr, False,
doExtra)
if not pg:
break
if pageCache:
pg = pageCache.getPage(pager, pageNr)
else:
pg = ctrl.sp.generatePMLPage(pager, pageNr, False,
doExtra)
if not pg:
break

sx = pageGap + pagesDone * (ctrl.pageW + pageGap)
sy = pageGap
sx += pagesDonePerRow * (ctrl.pageW + self.PAGE_GAP)

dp = DisplayPage(pageNr, sx, sy, sx + ctrl.pageW,
sy + ctrl.pageH)
dpages.append(dp)
dp = DisplayPage(pageNr, sx, sy, sx + ctrl.pageW,
sy + ctrl.pageH)
dpages.append(dp)

for op in pg.ops:
if not isinstance(op, textOp):
continue
for op in pg.ops:
if not isinstance(op, textOp):
continue

texts.append(TextString(op.line, op.text,
int(sx + op.x * mm2p), int(sy + op.y * mm2p),
cfgGui.fonts[op.flags & 3], op.flags & pml.UNDERLINED))
texts.append(TextString(op.line, op.text,
int(sx + op.x * mm2p), int(sy + op.y * mm2p),
cfgGui.fonts[op.flags & 3], op.flags & pml.UNDERLINED))

pageNr += 1
pagesDone += 1
pageNr += 1
pagesDonePerRow += 1
sy += ctrl.pageH + self.PAGE_GAP

return (texts, dpages)

def getLineHeight(self, ctrl):
# the + 1.0 avoids occasional non-consecutive backgrounds for
# lines.
return int(ctrl.chY * ctrl.mm2p + 1.0)
def getDocumentHeight(self, ctrl) -> int:
pagesPerRow = max(1, (ctrl.GetClientSize().GetWidth() - self.PAGE_GAP) // (ctrl.pageW + self.PAGE_GAP))
rows = math.ceil((len(ctrl.sp.pages) - 1) / pagesPerRow)

return int(rows * (ctrl.pageH + self.PAGE_GAP) + 2 * self.PAGE_GAP)

def getPageWidth(self, ctrl):
return (ctrl.sp.cfg.paperWidth / ctrl.chX) *\
Expand Down

0 comments on commit 650c749

Please sign in to comment.