Call gtk.main_quit in reader.quit.
[automanga.git] / manga / reader.py
1 import threading, gtk, gio, gobject
2 import lib
3
4 class notdone(Exception): pass
5
6 class future(threading.Thread):
7     prog = None
8
9     def __init__(self):
10         super(future, self).__init__()
11         self._val = None
12         self._exc = None
13         self.start()
14
15     def run(self):
16         try:
17             val = self.value()
18         except Exception as e:
19             self._exc = e
20         else:
21             self._val = [val]
22
23     @property
24     def val(self):
25         if self._exc is not None:
26             raise self._exc
27         if self._val is None:
28             raise notdone()
29         return self._val[0]
30
31     @property
32     def done(self):
33         return self._exc != None or self._val != None
34
35 class imgload(future):
36     def __init__(self, page):
37         self.page = page
38         self.st = None
39         super(imgload, self).__init__()
40
41     def value(self):
42         buf = bytearray()
43         with self.page.open() as st:
44             self.p = 0
45             self.st = st
46             while True:
47                 read = st.read(1024)
48                 if read == "":
49                     break
50                 self.p += len(read)
51                 buf.extend(read)
52         self.st = None
53         return gtk.gdk.pixbuf_new_from_stream(gio.memory_input_stream_new_from_data(str(buf)))
54
55     @property
56     def prog(self):
57         if self.st is None or self.st.clen is None:
58             return None
59         return float(self.p) / float(self.st.clen)
60
61 class pageview(gtk.Widget):
62     def __init__(self, pixbuf):
63         super(pageview, self).__init__()
64         self.pixbuf = pixbuf
65         self.zoomed = None, None
66         self.fit = True
67         self.zoom = 1.0
68         self.interp = gtk.gdk.INTERP_HYPER
69         self.off = 0, 0
70
71     def get_osize(self):
72         return self.pixbuf.get_width(), self.pixbuf.get_height()
73
74     def get_asize(self):
75         return self.allocation.width, self.allocation.height
76
77     def do_realize(self):
78         self.set_flags(self.flags() | gtk.REALIZED)
79         alloc = self.allocation
80         self.window = gtk.gdk.Window(self.get_parent_window(),
81                                      width=alloc.width, height=alloc.height,
82                                      window_type = gtk.gdk.WINDOW_CHILD,
83                                      wclass = gtk.gdk.INPUT_OUTPUT,
84                                      event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK
85                                      )
86         self.window.set_user_data(self)
87         self.style.attach(self.window)
88         self.style.set_background(self.window, gtk.STATE_NORMAL)
89         self.window.move_resize(*alloc)
90
91     def do_unrealize(self):
92         self.window.set_user_data(None)
93
94     def do_size_request(self, req):
95         req.width, req.height = self.get_osize()
96
97     def fitzoom(self):
98         w, h = self.get_osize()
99         alloc = self.allocation
100         return min(float(alloc.width) / float(w), float(alloc.height) / float(h))
101
102     def do_size_allocate(self, alloc):
103         self.allocation = alloc
104         if self.fit:
105             self.zoom = self.fitzoom()
106         if self.flags() & gtk.REALIZED:
107             self.window.move_resize(*alloc)
108
109     def get_zoomed(self):
110         zoom = self.zoom
111         pz, zbuf = self.zoomed
112         if pz != zoom:
113             w, h = self.get_osize()
114             zbuf = self.pixbuf.scale_simple(int(w * zoom), int(h * zoom), self.interp)
115             self.zoomed = zoom, zbuf
116         return zbuf
117
118     def get_zsize(self):
119         zbuf = self.get_zoomed()
120         return zbuf.get_width(), zbuf.get_height()
121
122     def do_expose_event(self, event):
123         aw, ah = self.get_asize()
124         dw, dh = aw, ah
125         zbuf = self.get_zoomed()
126         zw, zh = self.get_zsize()
127         ox, oy = self.off
128         dx, dy = 0, 0
129         if zw < aw:
130             dx = (aw - zw) / 2
131             dw = zw
132         if zh < ah:
133             dy = (ah - zh) / 2
134             dh = zh
135         gc = self.style.fg_gc[gtk.STATE_NORMAL]
136         self.window.draw_pixbuf(gc, zbuf, ox, oy, dx, dy, dw, dh)
137
138     def set_off(self, off):
139         aw, ah = self.get_asize()
140         zw, zh = self.get_zsize()
141         ox, oy = off
142         ox, oy = int(ox), int(oy)
143         if ox > zw - aw: ox = zw - aw
144         if oy > zh - ah: oy = zh - ah
145         if ox < 0: ox = 0
146         if oy < 0: oy = 0
147         self.off = ox, oy
148         self.queue_draw()
149
150     def set_zoom(self, zoom):
151         if zoom is not None: zoom = float(zoom)
152         aw, ah = self.get_asize()
153         zw, zh = self.get_zsize()
154         dw, dh = zw - aw, zh - ah
155         ox, oy = self.off
156         xa = float(ox) / float(dw) if dw > 0 else 0.5
157         ya = float(oy) / float(dh) if dh > 0 else 0.5
158
159         if zoom is None:
160             self.fit = True
161             self.zoom = self.fitzoom()
162         else:
163             self.fit = False
164             self.zoom = zoom
165
166         zw, zh = self.get_zsize()
167         dw, dh = zw - aw, zh - ah
168         ox = int(xa * dw) if dw > 0 else 0
169         oy = int(ya * dh) if dh > 0 else 0
170         self.set_off((ox, oy))
171 gobject.type_register(pageview)
172
173 class reader(gtk.Window):
174     def __init__(self, manga):
175         super(reader, self).__init__(gtk.WINDOW_TOPLEVEL)
176         self.connect("delete_event",    lambda wdg, ev, data=None: False)
177         self.connect("destroy",         lambda wdg, data=None:     self.quit())
178         self.connect("key_press_event", self.key)
179         self.pfr = gtk.Frame(None)
180         self.pfr.set_shadow_type(gtk.SHADOW_NONE)
181         self.add(self.pfr)
182         self.pfr.show()
183
184         self.manga = manga
185         self.page = None
186         self.cursor = lib.cursor(manga)
187         self.setpage(self.cursor.cur)
188         self.updtitle()
189
190     def setpage(self, page):
191         if self.page is not None:
192             self.pfr.remove(self.page)
193             self.page = None
194         if page is not None:
195             with self.cursor.cur.open() as inp:
196                 pb = gtk.gdk.pixbuf_new_from_stream(gio.memory_input_stream_new_from_data(inp.read()))
197             self.page = pageview(pb)
198             self.pfr.add(self.page)
199             self.page.show()
200
201     def updtitle(self):
202         self.set_title(u"Automanga \u2013 " + self.manga.name)
203
204     @property
205     def zoom(self):
206         return self.page.zoom
207     @zoom.setter
208     def zoom(self, zoom):
209         self.page.set_zoom(zoom)
210
211     def pan(self, off):
212         ox, oy = self.page.off
213         px, py = off
214         self.page.set_off((ox + px, oy + py))
215
216     def key(self, wdg, ev, data=None):
217         if ev.keyval in [ord('Q'), ord('q'), 65307]:
218             self.quit()
219         if ev.keyval in [ord('O'), ord('o')]:
220             self.zoom = 1.0
221         elif ev.keyval in [ord('P'), ord('p')]:
222             self.zoom = None
223         elif ev.keyval in [ord('[')]:
224             self.zoom = min(self.zoom * 1.25, 3)
225         elif ev.keyval in [ord(']')]:
226             self.zoom /= 1.25
227         elif ev.keyval in [ord('h')]:
228             self.pan((-100, 0))
229         elif ev.keyval in [ord('j')]:
230             self.pan((0, 100))
231         elif ev.keyval in [ord('k')]:
232             self.pan((0, -100))
233         elif ev.keyval in [ord('l')]:
234             self.pan((100, 0))
235         elif ev.keyval in [ord('H')]:
236             self.page.set_off((0, self.page.off[1]))
237         elif ev.keyval in [ord('J')]:
238             self.page.set_off((self.page.off[0], self.page.get_asize()[1]))
239         elif ev.keyval in [ord('K')]:
240             self.page.set_off((self.page.off[1], 0))
241         elif ev.keyval in [ord('L')]:
242             self.page.set_off((self.page.get_asize()[0], self.page.off[1]))
243         elif ev.keyval in [ord(' ')]:
244             self.setpage(self.cursor.next())
245         elif ev.keyval in [65288]:
246             self.setpage(self.cursor.prev())
247
248     def quit(self):
249         self.hide()
250         gtk.main_quit()
251 gobject.type_register(reader)