1 import threading, gtk, gio, gobject
4 class notdone(Exception): pass
6 class future(threading.Thread):
10 super(future, self).__init__()
18 super(future, self).start()
24 except Exception as e:
27 for cb in self._notlist:
33 for cb in self._notlist:
37 # Caller must hold GDK lock
41 self._notlist.append(cb)
48 for cb in self._notlist:
56 if self._exc is not None:
65 return self._exc != None or self._val != None
69 while self.is_alive():
73 class imgload(future):
74 def __init__(self, page):
75 super(imgload, self).__init__()
82 with self.page.open() as st:
93 return gtk.gdk.pixbuf_new_from_stream(gio.memory_input_stream_new_from_data(str(buf)))
97 if self.st is None or self.st.clen is None:
99 return float(self.p) / float(self.st.clen)
101 class pagecache(object):
102 def __init__(self, sz=50):
106 def __getitem__(self, page):
108 for ol, f in self.bk:
112 self.bk.append((idl, f))
113 if len(self.bk) > self.sz:
114 self.bk = self.bk[-sz:]
117 class relpageget(future):
118 def __init__(self, cur, prev, cache=None):
119 super(relpageget, self).__init__()
120 self.cur = lib.cursor(cur)
128 page = self.cur.prev()
130 page = self.cur.next()
131 except StopIteration:
138 class pageget(future):
139 def __init__(self, fnode):
140 super(pageget, self).__init__()
145 return lib.cursor(self.fnode).cur
147 class ccursor(object):
148 def __init__(self, ob, cache=None):
149 self.cur = lib.cursor(ob)
150 self.prev = relpageget(self.cur, True, cache)
151 self.next = relpageget(self.cur, False, cache)
153 class pageview(gtk.Widget):
154 def __init__(self, pixbuf):
155 super(pageview, self).__init__()
157 self.zoomed = None, None
160 self.interp = gtk.gdk.INTERP_HYPER
164 return self.pixbuf.get_width(), self.pixbuf.get_height()
167 return self.allocation.width, self.allocation.height
169 def do_realize(self):
170 self.set_flags(self.flags() | gtk.REALIZED)
171 alloc = self.allocation
172 self.window = gtk.gdk.Window(self.get_parent_window(),
173 width=alloc.width, height=alloc.height,
174 window_type = gtk.gdk.WINDOW_CHILD,
175 wclass = gtk.gdk.INPUT_OUTPUT,
176 event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK
178 self.window.set_user_data(self)
179 self.style.attach(self.window)
180 self.style.set_background(self.window, gtk.STATE_NORMAL)
181 self.window.move_resize(*alloc)
183 def do_unrealize(self):
184 self.window.set_user_data(None)
186 def do_size_request(self, req):
187 req.width, req.height = self.get_osize()
190 w, h = self.get_osize()
191 alloc = self.allocation
192 return min(float(alloc.width) / float(w), float(alloc.height) / float(h))
194 def do_size_allocate(self, alloc):
195 self.allocation = alloc
197 self.zoom = self.fitzoom()
198 if self.flags() & gtk.REALIZED:
199 self.window.move_resize(*alloc)
201 def get_zoomed(self):
203 pz, zbuf = self.zoomed
205 w, h = self.get_osize()
206 zbuf = self.pixbuf.scale_simple(int(w * zoom), int(h * zoom), self.interp)
207 self.zoomed = zoom, zbuf
211 zbuf = self.get_zoomed()
212 return zbuf.get_width(), zbuf.get_height()
214 def do_expose_event(self, event):
215 aw, ah = self.get_asize()
217 zbuf = self.get_zoomed()
218 zw, zh = self.get_zsize()
227 gc = self.style.fg_gc[gtk.STATE_NORMAL]
228 self.window.draw_pixbuf(gc, zbuf, ox, oy, dx, dy, dw, dh)
230 def set_off(self, off):
231 aw, ah = self.get_asize()
232 zw, zh = self.get_zsize()
234 ox, oy = int(ox), int(oy)
235 if ox > zw - aw: ox = zw - aw
236 if oy > zh - ah: oy = zh - ah
242 def set_zoom(self, zoom):
243 if zoom is not None: zoom = float(zoom)
244 aw, ah = self.get_asize()
245 zw, zh = self.get_zsize()
246 dw, dh = zw - aw, zh - ah
248 xa = float(ox) / float(dw) if dw > 0 else 0.5
249 ya = float(oy) / float(dh) if dh > 0 else 0.5
253 self.zoom = self.fitzoom()
258 zw, zh = self.get_zsize()
259 dw, dh = zw - aw, zh - ah
260 ox = int(xa * dw) if dw > 0 else 0
261 oy = int(ya * dh) if dh > 0 else 0
262 self.set_off((ox, oy))
263 gobject.type_register(pageview)
265 class pagefetch(object):
266 def __init__(self, fpage):
269 def attach(self, reader):
271 self.msg = gtk.Alignment(0, 0.5, 0, 0)
272 self.hlay = gtk.HBox()
273 self.lbl = gtk.Label("Fetching page...")
274 self.hlay.pack_start(self.lbl)
276 self.msg.add(self.hlay)
278 self.rd.sbar.pack_start(self.msg)
281 self.pg.notify(self.haspage)
284 if self.rd.pagefetch.cur != self: return False
287 if self.pg.val is not None:
288 self.rd.setpage(self.pg.val)
289 self.rd.pagefetch.set(None)
292 self.rd.sbar.remove(self.msg)
294 class imgfetch(object):
295 def __init__(self, fimage):
300 def attach(self, reader):
302 self.msg = gtk.Alignment(0, 0.5, 0, 0)
303 self.hlay = gtk.HBox()
304 self.lbl = gtk.Label("Fetching image...")
305 self.hlay.pack_start(self.lbl)
307 self.prog = gtk.ProgressBar()
308 self.prog.set_fraction(0.0)
309 self.hlay.pack_start(self.prog)
311 self.msg.add(self.hlay)
313 self.rd.sbar.pack_start(self.msg)
316 self.img.notify(self.imgprog)
319 if self.rd.imgfetch.cur != self: return False
323 except Exception as e:
328 self.rd.imgfetch.set(None)
331 if p: self.prog.set_fraction(p)
335 self.rd.sbar.remove(self.msg)
338 if self.error is not None:
339 self.rd.pagelbl.set_text("Error fetching image: " + self.error)
341 class procslot(object):
342 __slots__ = ["cur", "p"]
343 def __init__(self, p):
348 if self.cur is not None:
359 class reader(gtk.Window):
360 def __init__(self, manga):
361 super(reader, self).__init__(gtk.WINDOW_TOPLEVEL)
362 self.connect("delete_event", lambda wdg, ev, data=None: False)
363 self.connect("destroy", lambda wdg, data=None: self.quit())
364 self.connect("key_press_event", self.key)
365 self.cache = pagecache()
366 self.pagefetch = procslot(self)
367 self.imgfetch = procslot(self)
370 self.pfr = gtk.Frame(None)
371 self.pfr.set_shadow_type(gtk.SHADOW_NONE)
372 vlay.pack_start(self.pfr)
374 self.sbar = gtk.HBox()
375 self.pagelbl = gtk.Label("")
376 algn = gtk.Alignment(0, 0.5, 0, 0)
377 algn.add(self.pagelbl)
379 self.sbar.pack_start(algn)
381 vlay.pack_end(self.sbar, False)
388 self.fetchpage(pageget(self.manga))
392 def updpagelbl(self):
393 if self.page is None:
394 self.pagelbl.set_text("")
396 w, h = self.page.get_osize()
397 self.pagelbl.set_text(u"%s\u00d7%s (%d%%)" % (w, h, int(self.page.zoom * 100)))
399 def setimg(self, img):
400 if self.page is not None:
401 self.pfr.remove(self.page)
404 self.page = pageview(img)
405 self.pfr.add(self.page)
409 def setpage(self, page):
410 if self.point is not None:
413 self.point = ccursor(page, self.cache)
414 self.imgfetch.set(imgfetch(self.cache[page]))
418 def fetchpage(self, fpage):
419 self.imgfetch.set(None)
420 self.pagefetch.set(pagefetch(fpage))
423 self.set_title(u"Automanga \u2013 " + self.manga.name)
427 return self.page.zoom
429 def zoom(self, zoom):
430 self.page.set_zoom(zoom)
434 ox, oy = self.page.off
436 self.page.set_off((ox + px, oy + py))
438 def key(self, wdg, ev, data=None):
439 if ev.keyval in [ord('Q'), ord('q')]:
441 elif ev.keyval in [65307]:
442 if self.page is not None:
443 self.pagefetch.set(None)
444 self.imgfetch.set(None)
445 if self.page is not None:
446 if ev.keyval in [ord('O'), ord('o')]:
448 elif ev.keyval in [ord('P'), ord('p')]:
450 elif ev.keyval in [ord('[')]:
451 self.zoom = min(self.zoom * 1.25, 3)
452 elif ev.keyval in [ord(']')]:
454 elif ev.keyval in [ord('h')]:
456 elif ev.keyval in [ord('j')]:
458 elif ev.keyval in [ord('k')]:
460 elif ev.keyval in [ord('l')]:
462 elif ev.keyval in [ord('H')]:
463 self.page.set_off((0, self.page.off[1]))
464 elif ev.keyval in [ord('J')]:
465 self.page.set_off((self.page.off[0], self.page.get_asize()[1]))
466 elif ev.keyval in [ord('K')]:
467 self.page.set_off((self.page.off[1], 0))
468 elif ev.keyval in [ord('L')]:
469 self.page.set_off((self.page.get_asize()[0], self.page.off[1]))
470 if self.point is not None:
471 if ev.keyval in [ord(' ')]:
472 self.fetchpage(self.point.next)
473 elif ev.keyval in [65288]:
474 self.fetchpage(self.point.prev)
479 gobject.type_register(reader)