X-Git-Url: http://dolda2000.com/gitweb/?a=blobdiff_plain;f=manga%2Freader.py;h=c4d322d896f62a96ece5a09f12d8c65b43b5d826;hb=ca0d6a2e71303c12c76f6edc9b17c15456bcf4f3;hp=d9d8851e14ab5d642be4d16861e55477f75d0dd6;hpb=c9e2eced035fe19a05a60bb5435d050407e8254c;p=automanga.git diff --git a/manga/reader.py b/manga/reader.py index d9d8851..c4d322d 100644 --- a/manga/reader.py +++ b/manga/reader.py @@ -1,5 +1,8 @@ -import threading, gtk, gio, gobject -import lib +import threading +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk as gtk, GdkPixbuf as gdkpix, Gdk as gdk, GObject as gobject +from . import lib, profile class notdone(Exception): pass @@ -11,28 +14,30 @@ class future(threading.Thread): self._val = None self._exc = None self._notlist = [] - self._started = False + self._tstarted = False + self.setDaemon(True) def start(self): - if not self._started: + if not self._tstarted: super(future, self).start() - self._started = True + self._tstarted = True def run(self): try: val = self.value() except Exception as e: - with gtk.gdk.lock: - self._exc = e - for cb in self._notlist: - cb() - self._notlist = [] + self._exc = e + gobject.idle_add(self._callcbs, True) else: - with gtk.gdk.lock: - self._val = [val] - for cb in self._notlist: - cb() - self._notlist = [] + self._val = [val] + gobject.idle_add(self._callcbs, True) + + def _callcbs(self, final): + nls = [] + for cb in self._notlist: + if cb(): + nls.append(cb) + self._notlist = [] if final else nls # Caller must hold GDK lock def notify(self, cb): @@ -43,12 +48,7 @@ class future(threading.Thread): cb() def progcb(self): - with gtk.gdk.lock: - nls = [] - for cb in self._notlist: - if cb(): - nls.append(cb) - self._notlist = nls + gobject.idle_add(self._callcbs, False) @property def val(self): @@ -78,19 +78,28 @@ class imgload(future): self.start() def value(self): - buf = bytearray() - with self.page.open() as st: - self.p = 0 - self.st = st - while True: - read = st.read(1024) - if read == "": - break - self.p += len(read) - buf.extend(read) - self.progcb() - self.st = None - return gtk.gdk.pixbuf_new_from_stream(gio.memory_input_stream_new_from_data(str(buf))) + buf = gdkpix.PixbufLoader() + done = False + try: + with self.page.open() as st: + self.p = 0 + self.st = st + while True: + read = st.read(1024) + if read == b"": + break + self.p += len(read) + buf.write(read) + self.progcb() + self.st = None + done = True + finally: + try: + buf.close() + except: + if done: + raise + return buf.get_pixbuf() @property def prog(self): @@ -111,9 +120,17 @@ class pagecache(object): f = imgload(page) self.bk.append((idl, f)) if len(self.bk) > self.sz: - self.bk = self.bk[-sz:] + self.bk = self.bk[-self.sz:] return f + def __delitem__(self, page): + idl = page.idlist() + for i, (ol, f) in enumerate(self.bk): + if ol == idl: + del self.bk[i] + return + raise KeyError(idl) + class relpageget(future): def __init__(self, cur, prev, cache=None): super(relpageget, self).__init__() @@ -135,6 +152,18 @@ class relpageget(future): self.cache[page] return page +class idpageget(future): + def __init__(self, base, idlist): + super(idpageget, self).__init__() + self.bnode = base + self.idlist = idlist + + def value(self): + try: + return lib.cursor(self.bnode.byidlist(self.idlist)).cur + except KeyError: + raise KeyError("could not find last read page: " + repr(self.idlist)) + class pageget(future): def __init__(self, fnode): super(pageget, self).__init__() @@ -157,46 +186,59 @@ class pageview(gtk.Widget): self.zoomed = None, None self.fit = True self.zoom = 1.0 - self.interp = gtk.gdk.INTERP_HYPER + self.interp = gdkpix.InterpType.HYPER self.off = 0, 0 def get_osize(self): return self.pixbuf.get_width(), self.pixbuf.get_height() def get_asize(self): - return self.allocation.width, self.allocation.height + alloc = self.get_allocation() + return alloc.width, alloc.height def do_realize(self): - self.set_flags(self.flags() | gtk.REALIZED) - alloc = self.allocation - self.window = gtk.gdk.Window(self.get_parent_window(), - width=alloc.width, height=alloc.height, - window_type = gtk.gdk.WINDOW_CHILD, - wclass = gtk.gdk.INPUT_OUTPUT, - event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK - ) - self.window.set_user_data(self) - self.style.attach(self.window) - self.style.set_background(self.window, gtk.STATE_NORMAL) - self.window.move_resize(*alloc) - - def do_unrealize(self): - self.window.set_user_data(None) - - def do_size_request(self, req): - req.width, req.height = self.get_osize() + alloc = self.get_allocation() + attr = gdk.WindowAttr() + attr.window_type = gdk.WindowType.CHILD + attr.x = alloc.x + attr.y = alloc.y + attr.width = alloc.width + attr.height = alloc.height + attr.visual = self.get_visual() + attr.event_mask = self.get_events() | gdk.EventMask.EXPOSURE_MASK + a = gdk.WindowAttributesType + wnd = gdk.Window(self.get_parent_window(), attr, a.X | a.Y | a.VISUAL) + wnd.set_background_pattern(None) + self.set_window(wnd) + self.register_window(wnd) + self.set_realized(True) + + # XXX: Why don't these actually get called? + def get_preferred_width(self): + return 0, max(min(self.get_osize()[0], 4096), 0) + def get_preferred_height(self): + return 0, max(min(self.get_osize()[1], 4096), 0) def fitzoom(self): w, h = self.get_osize() - alloc = self.allocation + alloc = self.get_allocation() return min(float(alloc.width) / float(w), float(alloc.height) / float(h)) def do_size_allocate(self, alloc): - self.allocation = alloc + self.set_allocation(alloc) if self.fit: self.zoom = self.fitzoom() - if self.flags() & gtk.REALIZED: - self.window.move_resize(*alloc) + else: + aw, ah = self.get_asize() + zw, zh = self.get_zsize() + ox, oy = self.off + if zw >= aw and ox + aw > zw: + ox = zw - aw + if zh >= ah and oy + ah > zh: + oy = zh - ah + self.off = ox, oy + if self.get_realized(): + self.get_window().move_resize(alloc.x, alloc.y, alloc.width, alloc.height) def get_zoomed(self): zoom = self.zoom @@ -211,7 +253,7 @@ class pageview(gtk.Widget): zbuf = self.get_zoomed() return zbuf.get_width(), zbuf.get_height() - def do_expose_event(self, event): + def do_draw(self, cr): aw, ah = self.get_asize() dw, dh = aw, ah zbuf = self.get_zoomed() @@ -224,8 +266,8 @@ class pageview(gtk.Widget): if zh < ah: dy = (ah - zh) / 2 dh = zh - gc = self.style.fg_gc[gtk.STATE_NORMAL] - self.window.draw_pixbuf(gc, zbuf, ox, oy, dx, dy, dw, dh) + gdk.cairo_set_source_pixbuf(cr, zbuf, dx - ox, dy - oy) + cr.paint() def set_off(self, off): aw, ah = self.get_asize() @@ -260,24 +302,43 @@ class pageview(gtk.Widget): ox = int(xa * dw) if dw > 0 else 0 oy = int(ya * dh) if dh > 0 else 0 self.set_off((ox, oy)) -gobject.type_register(pageview) - -class pagefetch(object): - def __init__(self, fpage): - self.pg = fpage +class msgproc(object): def attach(self, reader): self.rd = reader - self.msg = gtk.Alignment(0, 0.5, 0, 0) + self.msg = gtk.Alignment(xalign=0.0, yalign=0.5, xscale=0.0, yscale=0.0) self.hlay = gtk.HBox() - self.lbl = gtk.Label("Fetching page...") - self.hlay.pack_start(self.lbl) + self.lbl = gtk.Label("") + self.hlay.pack_start(self.lbl, True, True, 0) self.lbl.show() self.msg.add(self.hlay) self.hlay.show() - self.rd.sbar.pack_start(self.msg) + self.rd.sbar.pack_start(self.msg, True, True, 0) self.msg.show() + self._prog = None + + def prog(self, p): + if p is not None: + if self._prog is None: + self._prog = gtk.ProgressBar() + self.hlay.pack_start(self._prog, True, True, 5) + self._prog.show() + self._prog.set_fraction(p) + elif p is None and self._prog is not None: + self.hlay.remove(self._prog) + self._prog = None + + def abort(self): + self.rd.sbar.remove(self.msg) +class pagefetch(msgproc): + def __init__(self, fpage, setcb=None): + self.pg = fpage + self.setcb = setcb + + def attach(self, reader): + super(pagefetch, self).attach(reader) + self.lbl.set_text("Fetching page...") self.pg.notify(self.haspage) def haspage(self): @@ -286,33 +347,19 @@ class pagefetch(object): return True if self.pg.val is not None: self.rd.setpage(self.pg.val) + if self.setcb is not None: + self.setcb(self.pg.val) self.rd.pagefetch.set(None) - def abort(self): - self.rd.sbar.remove(self.msg) - -class imgfetch(object): +class imgfetch(msgproc): def __init__(self, fimage): self.img = fimage self.upd = False self.error = None def attach(self, reader): - self.rd = reader - self.msg = gtk.Alignment(0, 0.5, 0, 0) - self.hlay = gtk.HBox() - self.lbl = gtk.Label("Fetching image...") - self.hlay.pack_start(self.lbl) - self.lbl.show() - self.prog = gtk.ProgressBar() - self.prog.set_fraction(0.0) - self.hlay.pack_start(self.prog) - self.prog.show() - self.msg.add(self.hlay) - self.hlay.show() - self.rd.sbar.pack_start(self.msg) - self.msg.show() - + super(imgfetch, self).attach(reader) + self.lbl.set_text("Fetching image...") self.img.notify(self.imgprog) def imgprog(self): @@ -327,8 +374,7 @@ class imgfetch(object): self.upd = True self.rd.imgfetch.set(None) else: - p = self.img.prog - if p: self.prog.set_fraction(p) + self.prog(self.img.prog) return True def abort(self): @@ -338,6 +384,36 @@ class imgfetch(object): if self.error is not None: self.rd.pagelbl.set_text("Error fetching image: " + self.error) +class preload(msgproc): + def __init__(self, fpage): + self.pg = fpage + + def attach(self, reader): + super(preload, self).attach(reader) + self.lbl.set_text("Fetching next page...") + self.pg.notify(self.haspage) + + def haspage(self): + if self.rd.preload.cur != self: return False + if not self.pg.done: return True + if self.pg.val is not None: + self.img = self.rd.cache[self.pg.val] + self.lbl.set_text("Loading next page...") + self.img.notify(self.imgprog) + else: + self.rd.preload.set(None) + + def imgprog(self): + if self.rd.preload.cur != self: return False + if self.img.done: + self.rd.preload.set(None) + else: + self.prog(self.img.prog) + return True + + def abort(self): + self.rd.sbar.remove(self.msg) + class procslot(object): __slots__ = ["cur", "p"] def __init__(self, p): @@ -356,45 +432,149 @@ class procslot(object): self.cur = None raise +class plistget(future): + def __init__(self, node): + super(plistget, self).__init__() + self.node = node + + def value(self): + return list(self.node) + +class loadplist(object): + def __init__(self, pnode): + self.pnode = pnode + self.flist = plistget(self.pnode) + + def attach(self, sbox): + self.sbox = sbox + self.flist.notify(self.haslist) + + def haslist(self): + if self.sbox.loadlist.cur != self: return False + if not self.flist.done: return True + self.sbox.setlist(self.flist.val) + +class sbox(gtk.ComboBox): + def __init__(self, reader, ptnode): + super(sbox, self).__init__() + self.rd = reader + self.node = ptnode + self.pnode, self.pidx = self.node.stack[-1] + + self.bk = gtk.ListStore(str) + self.set_model(self.bk) + cell = gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, "text", 0) + self.set_active(0) + + self.set_sensitive(False) + self.set_focus_on_click(False) + self.bk.append([ptnode.name]) + self.loadlist = procslot(self) + self.loadlist.set(loadplist(self.pnode)) + + def setlist(self, ls): + self.bk.clear() + for i, ch in enumerate(ls): + self.bk.append(["%i/%i: %s" % (i + 1, len(ls), ch.name)]) + self.set_active(self.pidx) + self.set_sensitive(True) + self.connect("changed", self.changed_cb) + + def changed_cb(self, wdg, data=None): + self.rd.fetchpage(pageget(self.pnode[self.get_active()])) + +class profprop(object): + def __init__(self, key, default=None): + self.key = key + self.default = default + + def __get__(self, ins, cls): + return ins.profile.props.get(self.key, self.default) + + def __set__(self, ins, val): + ins.profile.props[self.key] = val + ins.profile.save() + + def __delete__(self, ins): + del ins.profile.props[self.key] + ins.profile.save() + class reader(gtk.Window): - def __init__(self, manga): - super(reader, self).__init__(gtk.WINDOW_TOPLEVEL) + def __init__(self, manga, prof=None): + super(reader, self).__init__() self.connect("delete_event", lambda wdg, ev, data=None: False) self.connect("destroy", lambda wdg, data=None: self.quit()) self.connect("key_press_event", self.key) self.cache = pagecache() self.pagefetch = procslot(self) self.imgfetch = procslot(self) + self.preload = procslot(self) + self.profile = prof if prof else profile.memmanga(None, None, manga.id) + + self.manga = manga + self.page = None + self.sboxes = [] + self.point = None vlay = gtk.VBox() - self.pfr = gtk.Frame(None) - self.pfr.set_shadow_type(gtk.SHADOW_NONE) - vlay.pack_start(self.pfr) + self.pfr = gtk.Frame() + self.pfr.set_shadow_type(gtk.ShadowType.NONE) + vlay.pack_start(self.pfr, True, True, 0) self.pfr.show() + self.sboxbar = gtk.HBox() + algn = gtk.Alignment(xalign=0.0, yalign=0.5, xscale=0.0, yscale=0.0) + sboxlbl = gtk.Label(self.manga.name + ": ") + algn.add(sboxlbl) + sboxlbl.show() + self.sboxbar.pack_start(algn, False, True, 0) + algn.show() + vlay.pack_start(self.sboxbar, False, True, 0) + self.sboxbar.show() self.sbar = gtk.HBox() self.pagelbl = gtk.Label("") - algn = gtk.Alignment(0, 0.5, 0, 0) + algn = gtk.Alignment(xalign=0.0, yalign=0.5, xscale=0.0, yscale=0.0) algn.add(self.pagelbl) self.pagelbl.show() - self.sbar.pack_start(algn) + self.sbar.pack_start(algn, True, True, 0) algn.show() - vlay.pack_end(self.sbar, False) + vlay.pack_end(self.sbar, False, True, 0) self.sbar.show() self.add(vlay) vlay.show() - self.manga = manga - self.page = None - self.fetchpage(pageget(self.manga)) + if self.curpage is not None: + self.fetchpage(idpageget(self.manga, self.curpage)) + else: + self.fetchpage(pageget(self.manga)) self.updtitle() - self.point = None + + zmode = profprop("zmode", "fit") + curpage = profprop("curpage") def updpagelbl(self): if self.page is None: self.pagelbl.set_text("") else: w, h = self.page.get_osize() - self.pagelbl.set_text(u"%s\u00d7%s (%d%%)" % (w, h, int(self.page.zoom * 100))) + self.pagelbl.set_text("%s\u00d7%s (%d%%)" % (w, h, int(self.page.zoom * 100))) + + def updsboxes(self, page): + nodes = [node for node, idx in page.stack[1:]] + [page] + l = min(len(self.sboxes), len(nodes)) + for i, (pbox, node) in enumerate(zip(self.sboxes, nodes)): + if pbox.node != node: + l = i + break + for i in range(l, len(self.sboxes)): + self.sboxbar.remove(self.sboxes[i]) + self.sboxes = self.sboxes[:l] + for i in range(l, len(nodes)): + new = sbox(self, nodes[i]) + self.sboxbar.pack_start(new, False, True, 5) + self.sboxes.append(new) + new.show() def setimg(self, img): if self.page is not None: @@ -402,6 +582,9 @@ class reader(gtk.Window): self.page = None if img is not None: self.page = pageview(img) + if self.zmode == "1": + self.page.set_zoom(1) + self.page.set_off((0, 0)) self.pfr.add(self.page) self.page.show() self.updpagelbl() @@ -410,17 +593,21 @@ class reader(gtk.Window): if self.point is not None: self.point = None if page is not None: + self.curpage = page.idlist() self.point = ccursor(page, self.cache) self.imgfetch.set(imgfetch(self.cache[page])) else: self.setimg(None) + self.updsboxes(page) - def fetchpage(self, fpage): + def fetchpage(self, fpage, setcb=None): self.imgfetch.set(None) - self.pagefetch.set(pagefetch(fpage)) + proc = pagefetch(fpage, setcb) + self.pagefetch.set(proc) + return proc def updtitle(self): - self.set_title(u"Automanga \u2013 " + self.manga.name) + self.set_title("Automanga \u2013 " + self.manga.name) @property def zoom(self): @@ -445,8 +632,10 @@ class reader(gtk.Window): if self.page is not None: if ev.keyval in [ord('O'), ord('o')]: self.zoom = 1.0 + self.zmode = "1" elif ev.keyval in [ord('P'), ord('p')]: self.zoom = None + self.zmode = "fit" elif ev.keyval in [ord('[')]: self.zoom = min(self.zoom * 1.25, 3) elif ev.keyval in [ord(']')]: @@ -469,9 +658,19 @@ class reader(gtk.Window): self.page.set_off((self.page.get_asize()[0], self.page.off[1])) if self.point is not None: if ev.keyval in [ord(' ')]: - self.fetchpage(self.point.next) + if self.page and self.page.off[1] + self.page.get_asize()[1] < self.page.get_zsize()[1]: + self.pan((0, self.page.get_asize()[1] - 50)) + else: + self.fetchpage(self.point.next, lambda page: self.preload.set(preload(relpageget(page, False, self.cache)))) elif ev.keyval in [65288]: - self.fetchpage(self.point.prev) + if self.page and self.page.off[1] > 0: + self.pan((0, -(self.page.get_asize()[1] - 50))) + else: + self.fetchpage(self.point.prev, lambda page: self.preload.set(preload(relpageget(page, True, self.cache)))) + elif ev.keyval in [ord('R'), ord('r')]: + page = self.point.cur.cur + del self.cache[page] + self.imgfetch.set(imgfetch(self.cache[page])) def quit(self): self.hide()