Created
July 27, 2013 19:34
-
-
Save RavuAlHemio/6096000 to your computer and use it in GitHub Desktop.
Gresenter: the two-window PDF presentation viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# Gresenter: the two-window PDF presentation viewer | |
# One window: presentation (projector) | |
# Other window: presentation and notes (two different PDF files) side by side | |
# (laptop screen) | |
# | |
# Released into the public domain. | |
# http://creativecommons.org/publicdomain/zero/1.0/ | |
from gi.repository import Gtk, Gdk, Poppler, Pango | |
class DumbPdfWidget(Gtk.DrawingArea): | |
def __init__(self, doc): | |
Gtk.DrawingArea.__init__(self) | |
self.connect('draw', self._do_draw) | |
self._doc = doc | |
self._bgcolor = Gdk.RGBA(0, 0, 0, 1) | |
self._black = False | |
self._white = False | |
if self._doc.get_n_pages() < 1: | |
raise ValueError("Document has less than one page.") | |
self._pgnum = 0 | |
def _do_draw(self, widget, cr): | |
# f0tch p0ge | |
pg = self._doc.get_page(self._pgnum) | |
# fetch sizes | |
(ww, wh) = (self.get_allocated_width(), self.get_allocated_height()) | |
(pw, ph) = pg.get_size() | |
# calculate aspect | |
wf = ww / pw | |
hf = wh / ph | |
# fit scaling | |
scale = min(wf, hf) | |
xoff = 0 | |
yoff = 0 | |
if wf > hf: | |
xoff = (ww - scale*pw) / 2 | |
else: | |
yoff = (wh - scale*ph) / 2 | |
# draw the background first | |
cr.set_source_rgba(*list(self._bgcolor)) | |
cr.rectangle(0, 0, ww, wh) | |
cr.fill() | |
# transform | |
cr.translate(xoff, yoff) | |
cr.scale(scale, scale) | |
if self._black: | |
cr.set_source_rgba(0, 0, 0, 1) | |
cr.rectangle(0, 0, pw, ph) | |
cr.fill() | |
else: | |
# blank to white in any case | |
cr.set_source_rgba(1, 1, 1, 1) | |
cr.rectangle(0, 0, pw, ph) | |
cr.fill() | |
if not self._white: | |
# draw page | |
pg.render(cr) | |
def set_bgcolor(self, newclr): | |
self._bgcolor = newclr | |
self.queue_draw() | |
def get_bgcolor(self): | |
return self._bgcolor | |
def page_advance(self): | |
if self._pgnum >= self._doc.get_n_pages()-1: | |
return | |
self._pgnum += 1 | |
self.queue_draw() | |
def page_regress(self): | |
if self._pgnum < 1: | |
return | |
self._pgnum -= 1 | |
self.queue_draw() | |
def page_first(self): | |
self._pgnum = 0 | |
self.queue_draw() | |
def page_last(self): | |
self._pgnum = self._doc.get_n_pages()-1 | |
self.queue_draw() | |
def page_n(self, n): | |
if n < 0: | |
self.page_first() | |
elif n >= self._doc.get_n_pages(): | |
self.page_last() | |
else: | |
self._pgnum = n | |
self.queue_draw() | |
def toggle_white(self): | |
self._white = not self._white | |
self._black = False | |
self.queue_draw() | |
def toggle_black(self): | |
self._black = not self._black | |
self._white = False | |
self.queue_draw() | |
def status(self): | |
return (self._pgnum, self._doc.get_n_pages(), self._black, self._white) | |
class GresenterWindow: | |
def _destruction(self, window): | |
Gtk.main_quit() | |
def _keypress(self, wdgt, evt): | |
self._app.keypress(evt.keyval) | |
def __init__(self, app, title, docpath): | |
self._app = app | |
self._win = Gtk.Window() | |
self._win.set_title(title) | |
self._win.connect('destroy', self._destruction) | |
self._win.connect('key-press-event', self._keypress) | |
self._doc = Poppler.Document.new_from_file(docpath, None) | |
self._pv = DumbPdfWidget(self._doc) | |
self._win.add(self._pv) | |
def show(self): | |
self._win.show_all() | |
def hide(self): | |
self._win.hide() | |
def page_advance(self): | |
self._pv.page_advance() | |
def page_regress(self): | |
self._pv.page_regress() | |
def page_first(self): | |
self._pv.page_first() | |
def page_last(self): | |
self._pv.page_last() | |
def page_n(self, n): | |
self._pv.page_n(n) | |
def toggle_white(self): | |
self._pv.toggle_white() | |
def toggle_black(self): | |
self._pv.toggle_black() | |
def status(self): | |
return self._pv.status() | |
class GresenterAdvancedWindow(GresenterWindow): | |
def __init__(self, app, title, prespath, notepath): | |
self._app = app | |
self._gotostr = "" | |
self._presdoc = Poppler.Document.new_from_file(prespath, None) | |
self._notedoc = Poppler.Document.new_from_file(notepath, None) | |
prespgs = self._presdoc.get_n_pages() | |
notepgs = self._notedoc.get_n_pages() | |
if prespgs != notepgs: | |
raise ValueError("different number of pages (pres {0}, note {1})".format(prespgs, notepgs)) | |
self._win = Gtk.Window() | |
self._win.set_title(title) | |
self._win.connect('destroy', self._destruction) | |
self._win.connect('key-press-event', self._keypress) | |
self._pv = DumbPdfWidget(self._presdoc) | |
self._notepv = DumbPdfWidget(self._notedoc) | |
self._statlbl = Gtk.Label("hmm") | |
self._statlbl.override_font(Pango.FontDescription.from_string("Sans 24")) | |
self._bigbox = Gtk.VBox() | |
self._paneful = Gtk.HPaned() | |
self._paneful.pack1(self._notepv, True, False) | |
self._paneful.pack2(self._pv, True, False) | |
self._bigbox.pack_start(self._paneful, True, True, 0) | |
self._bigbox.pack_start(self._statlbl, False, False, 0) | |
self._win.add(self._bigbox) | |
self._updateStatus() | |
def _updateStatus(self): | |
(pgnum, pgcount, blk, wht) = self._app.pres_status() | |
txt = "{0}/{1}".format(pgnum+1, pgcount) | |
if blk: | |
txt += " BLACK" | |
if wht: | |
txt += " WHITE" | |
if len(self._gotostr) > 0: | |
txt += ", Go to: " + self._gotostr | |
self._statlbl.set_text(txt) | |
def page_advance(self): | |
GresenterWindow.page_advance(self) | |
self._notepv.page_advance() | |
self._updateStatus() | |
def page_regress(self): | |
GresenterWindow.page_regress(self) | |
self._notepv.page_regress() | |
self._updateStatus() | |
def page_first(self): | |
GresenterWindow.page_first(self) | |
self._notepv.page_first() | |
self._updateStatus() | |
def page_last(self): | |
GresenterWindow.page_last(self) | |
self._notepv.page_last() | |
self._updateStatus() | |
def page_n(self, n): | |
GresenterWindow.page_n(self, n) | |
self._notepv.page_n(n) | |
self._updateStatus() | |
def toggle_white(self): | |
self._updateStatus() | |
def toggle_black(self): | |
self._updateStatus() | |
def pressed_digit(self, n): | |
self._gotostr += "{0:d}".format(n) | |
self._updateStatus() | |
def pressed_enter(self): | |
self._app.page_n(int(self._gotostr) - 1) | |
self._gotostr = "" | |
self._updateStatus() | |
def pressed_escape(self): | |
self._gotostr = "" | |
self._updateStatus() | |
def pressed_backspace(self): | |
self._gotostr = self._gotostr[:-1] | |
self._updateStatus() | |
class GresenterApp: | |
def __init__(self, prespath, notepath): | |
self._preswin = GresenterWindow(self, "presentation", prespath) | |
self._notewin = GresenterAdvancedWindow(self, "notes", prespath, notepath) | |
def show(self): | |
self._preswin.show() | |
self._notewin.show() | |
def keypress(self, keyval): | |
if keyval in (Gdk.KEY_b, Gdk.KEY_B, Gdk.KEY_period, Gdk.KEY_KP_Decimal): | |
self._preswin.toggle_black() | |
self._notewin.toggle_black() | |
elif keyval in (Gdk.KEY_w, Gdk.KEY_W): | |
self._preswin.toggle_white() | |
self._notewin.toggle_white() | |
elif keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Up, Gdk.KEY_Left): | |
self._preswin.page_regress() | |
self._notewin.page_regress() | |
elif keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Down, Gdk.KEY_Right): | |
self._preswin.page_advance() | |
self._notewin.page_advance() | |
elif keyval == Gdk.KEY_Home: | |
self._preswin.page_first() | |
self._notewin.page_first() | |
elif keyval == Gdk.KEY_End: | |
self._preswin.page_last() | |
self._notewin.page_last() | |
elif keyval >= Gdk.KEY_0 and keyval <= Gdk.KEY_9: | |
self._notewin.pressed_digit(keyval - Gdk.KEY_0) | |
elif keyval >= Gdk.KEY_KP_0 and keyval <= Gdk.KEY_KP_9: | |
self._notewin.pressed_digit(keyval - Gdk.KEY_KP_0) | |
elif keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter): | |
self._notewin.pressed_enter() | |
elif keyval == Gdk.KEY_BackSpace: | |
self._notewin.pressed_backspace() | |
elif keyval == Gdk.KEY_Escape: | |
# do not support escape as "quit" | |
self._notewin.pressed_escape() | |
def page_n(self, n): | |
self._preswin.page_n(n) | |
self._notewin.page_n(n) | |
def pres_status(self): | |
return self._preswin.status() | |
if __name__ == '__main__': | |
import sys, os.path | |
if len(sys.argv) != 3: | |
print >> sys.stderr, "Usage: {0} SLIDES NOTES".format(sys.argv[0]) | |
sys.exit(1) | |
slides = "file://" + os.path.abspath(sys.argv[1]) | |
notes = "file://" + os.path.abspath(sys.argv[2]) | |
ga = GresenterApp(slides, notes) | |
ga.show() | |
Gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment