fix EOL spaces
Signed-off-by: Sascha Silbe <sascha-pgp@silbe.org>
Sascha Silbe authored 13 years ago
Gonzalo Odiard committed 13 years ago
3 | 3 | ]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-read"> |
4 | 4 | <path d="M27.904,11.023h-0.002 c0-0.002-1.71-2.053-9.376-2.504C10.86,8.07,6.843,10.121,6.84,10.122c-1.898,0.619-3.495,1.735-3.495,3.494v27.702 c0,2.025,1.235,3.494,3.495,3.494c0.003,0,4.41-1.35,10.004-1.35c5.589-0.001,11.061,2.253,11.061,2.253" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-linejoin="round" stroke-width="3.5"/> |
5 | 5 | <path d="M27.898,11.023 L27.898,11.023c0-0.002,1.715-2.053,9.377-2.504c7.668-0.449,11.686,1.602,11.688,1.603c1.897,0.619,3.494,1.735,3.494,3.494 v27.702c0,2.025-1.233,3.494-3.494,3.494c-0.003,0-4.409-1.35-10.004-1.35c-5.589-0.001-11.062,2.253-11.062,2.253" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-linejoin="round" stroke-width="3.5"/> |
6 | ||
6 | ||
7 | 7 | <line display="inline" fill="none" stroke="&stroke_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" x1="27.898" x2="27.9" y1="11.023" y2="45.717"/> |
8 | 8 | <path d=" M32.566,44.275c0,0-0.031,2.906-4.666,2.906c-4.632,0-4.663-2.906-4.663-2.906" display="inline" fill="none" stroke="&stroke_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5"/> |
9 | 9 | </g></svg> |
23 | 23 | |
24 | 24 | def set_current_page(self, n): |
25 | 25 | # When the book is being loaded, calling this does not help |
26 | # In such a situation, we go into a loop and try to load the | |
26 | # In such a situation, we go into a loop and try to load the | |
27 | 27 | # supplied page when the book has loaded completely |
28 | 28 | n += 1 |
29 | 29 | if self._ready: |
20 | 20 | |
21 | 21 | from epub import _Epub as Epub |
22 | 22 | from epubview import _View as EpubView |
23 | from jobs import _JobFind as JobFind⏎ | |
23 | from jobs import _JobFind as JobFind |
31 | 31 | self._ncxpath = None |
32 | 32 | self._basepath = None |
33 | 33 | self._tempdir = tempfile.mkdtemp() |
34 | ||
34 | ||
35 | 35 | if not self._verify(): |
36 | 36 | print 'Warning: This does not seem to be a valid epub file' |
37 | ||
37 | ||
38 | 38 | self._get_opf() |
39 | 39 | self._get_ncx() |
40 | ||
40 | ||
41 | 41 | ncxfile = self._zobject.open(self._ncxpath) |
42 | opffile = self._zobject.open(self._opfpath) | |
42 | opffile = self._zobject.open(self._opfpath) | |
43 | 43 | self._navmap = navmap.NavMap(opffile, ncxfile, self._basepath) |
44 | ||
44 | ||
45 | 45 | opffile = self._zobject.open(self._opfpath) |
46 | self._info = epubinfo.EpubInfo(opffile) | |
47 | ||
46 | self._info = epubinfo.EpubInfo(opffile) | |
47 | ||
48 | 48 | self._unzip() |
49 | ||
49 | ||
50 | 50 | def _unzip(self): |
51 | 51 | #self._zobject.extractall(path = self._tempdir) # This is broken upto python 2.7 |
52 | 52 | orig_cwd = os.getcwd() |
60 | 60 | self._zobject.extract(name) |
61 | 61 | os.chdir(orig_cwd) |
62 | 62 | |
63 | ||
63 | ||
64 | 64 | def _get_opf(self): |
65 | 65 | containerfile = self._zobject.open('META-INF/container.xml') |
66 | ||
66 | ||
67 | 67 | tree = etree.parse(containerfile) |
68 | 68 | root = tree.getroot() |
69 | ||
69 | ||
70 | 70 | for element in root.iterfind('.//{urn:oasis:names:tc:opendocument:xmlns:container}rootfile'): |
71 | 71 | if element.get('media-type') == 'application/oebps-package+xml': |
72 | 72 | self._opfpath = element.get('full-path') |
73 | ||
74 | if self._opfpath.rpartition('/')[0]: | |
73 | ||
74 | if self._opfpath.rpartition('/')[0]: | |
75 | 75 | self._basepath = self._opfpath.rpartition('/')[0] + '/' |
76 | 76 | else: |
77 | 77 | self._basepath = '' |
78 | ||
78 | ||
79 | 79 | containerfile.close() |
80 | 80 | |
81 | 81 | |
82 | 82 | def _get_ncx(self): |
83 | 83 | opffile = self._zobject.open(self._opfpath) |
84 | ||
84 | ||
85 | 85 | tree = etree.parse(opffile) |
86 | 86 | root = tree.getroot() |
87 | 87 | |
91 | 91 | for element in root.iterfind('.//{http://www.idpf.org/2007/opf}item'): |
92 | 92 | if element.get('id') == tocid: |
93 | 93 | self._ncxpath = self._basepath + element.get('href') |
94 | ||
94 | ||
95 | 95 | opffile.close() |
96 | 96 | |
97 | 97 | def _verify(self): |
98 | 98 | ''' |
99 | Method to crudely check to verify that what we | |
99 | Method to crudely check to verify that what we | |
100 | 100 | are dealing with is a epub file or not |
101 | 101 | ''' |
102 | 102 | if not os.path.exists(self._filepath): |
103 | 103 | return False |
104 | ||
104 | ||
105 | 105 | self._zobject = zipfile.ZipFile(self._filepath) |
106 | ||
106 | ||
107 | 107 | if not 'mimetype' in self._zobject.namelist(): |
108 | 108 | return False |
109 | ||
109 | ||
110 | 110 | mtypefile = self._zobject.open('mimetype') |
111 | 111 | mimetype = mtypefile.readline() |
112 | ||
112 | ||
113 | 113 | if not mimetype.startswith('application/epub+zip'): # Some files seem to have trailing characters |
114 | 114 | return False |
115 | ||
115 | ||
116 | 116 | return True |
117 | ||
117 | ||
118 | 118 | def get_toc_model(self): |
119 | 119 | ''' |
120 | 120 | Returns a GtkTreeModel representation of the |
121 | 121 | Epub table of contents |
122 | ''' | |
122 | ''' | |
123 | 123 | return self._navmap.get_gtktreestore() |
124 | ||
124 | ||
125 | 125 | def get_flattoc(self): |
126 | 126 | ''' |
127 | 127 | Returns a flat (linear) list of files to be |
128 | 128 | rendered. |
129 | ''' | |
129 | ''' | |
130 | 130 | return self._navmap.get_flattoc() |
131 | ||
131 | ||
132 | 132 | def get_basedir(self): |
133 | 133 | ''' |
134 | 134 | Returns the base directory where the contents of the |
135 | 135 | epub has been unzipped |
136 | 136 | ''' |
137 | 137 | return self._tempdir |
138 | ||
138 | ||
139 | 139 | def get_info(self): |
140 | 140 | ''' |
141 | 141 | Returns a EpubInfo object for the open Epub file |
142 | ''' | |
142 | ''' | |
143 | 143 | return self._info |
144 | ||
144 | ||
145 | 145 | def close(self): |
146 | 146 | ''' |
147 | Cleans up (closes open zip files and deletes uncompressed content of Epub. | |
148 | Please call this when a file is being closed or during application exit. | |
149 | ''' | |
147 | Cleans up (closes open zip files and deletes uncompressed content of Epub. | |
148 | Please call this when a file is being closed or during application exit. | |
149 | ''' | |
150 | 150 | self._zobject.close() |
151 | 151 | shutil.rmtree(self._tempdir) |
6 | 6 | self._tree = etree.parse(opffile) |
7 | 7 | self._root = self._tree.getroot() |
8 | 8 | self._e_metadata = self._root.find('{http://www.idpf.org/2007/opf}metadata') |
9 | ||
9 | ||
10 | 10 | self.title = self._get_title() |
11 | 11 | self.creator = self._get_creator() |
12 | 12 | self.date = self._get_date() |
15 | 15 | self.rights = self._get_rights() |
16 | 16 | self.identifier = self._get_identifier() |
17 | 17 | self.language = self._get_language() |
18 | ||
19 | ||
18 | ||
19 | ||
20 | 20 | def _get_data(self, tagname): |
21 | 21 | element = self._e_metadata.find(tagname) |
22 | 22 | return element.text |
23 | ||
23 | ||
24 | 24 | def _get_title(self): |
25 | 25 | try: |
26 | 26 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}title') |
27 | 27 | except AttributeError: |
28 | 28 | return None |
29 | ||
29 | ||
30 | 30 | return ret |
31 | ||
31 | ||
32 | 32 | def _get_creator(self): |
33 | 33 | try: |
34 | 34 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}creator') |
35 | 35 | except AttributeError: |
36 | return None | |
36 | return None | |
37 | 37 | return ret |
38 | ||
38 | ||
39 | 39 | def _get_date(self): |
40 | 40 | #TODO: iter |
41 | 41 | try: |
42 | 42 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}date') |
43 | 43 | except AttributeError: |
44 | 44 | return None |
45 | ||
45 | ||
46 | 46 | return ret |
47 | 47 | |
48 | 48 | def _get_source(self): |
50 | 50 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}source') |
51 | 51 | except AttributeError: |
52 | 52 | return None |
53 | ||
53 | ||
54 | 54 | return ret |
55 | 55 | |
56 | 56 | def _get_rights(self): |
58 | 58 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}rights') |
59 | 59 | except AttributeError: |
60 | 60 | return None |
61 | ||
61 | ||
62 | 62 | return ret |
63 | 63 | |
64 | 64 | def _get_identifier(self): |
65 | 65 | #TODO: iter |
66 | element = self._e_metadata.find('.//{http://purl.org/dc/elements/1.1/}identifier') | |
66 | element = self._e_metadata.find('.//{http://purl.org/dc/elements/1.1/}identifier') | |
67 | 67 | |
68 | 68 | if element is not None: |
69 | 69 | return {'id':element.get('id'), 'value':element.text} |
75 | 75 | ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}language') |
76 | 76 | except AttributeError: |
77 | 77 | return None |
78 | ||
78 | ||
79 | 79 | return ret |
80 | 80 | |
81 | 81 | def _get_subject(self): |
85 | 85 | subjectlist.append(element.text) |
86 | 86 | except AttributeError: |
87 | 87 | return None |
88 | ||
89 | return subjectlist⏎ | |
88 | ||
89 | return subjectlist |
36 | 36 | __gproperties__ = { |
37 | 37 | 'has-selection' : (gobject.TYPE_BOOLEAN, 'whether has selection', |
38 | 38 | 'whether the widget has selection or not', |
39 | 0, gobject.PARAM_READABLE), | |
39 | 0, gobject.PARAM_READABLE), | |
40 | 40 | 'zoom' : (gobject.TYPE_FLOAT, 'the zoom level', |
41 | 41 | 'the zoom level of the widget', |
42 | 42 | 0.5, 4.0, 1.0, gobject.PARAM_READWRITE) |
43 | } | |
43 | } | |
44 | 44 | __gsignals__ = { |
45 | 45 | 'page-changed': (gobject.SIGNAL_RUN_FIRST, |
46 | 46 | gobject.TYPE_NONE, |
47 | 47 | ([])) |
48 | } | |
48 | } | |
49 | 49 | def __init__(self): |
50 | 50 | gobject.threads_init() |
51 | 51 | gtk.HBox.__init__(self) |
52 | ||
52 | ||
53 | 53 | self.connect("destroy", self._destroy_cb) |
54 | 54 | |
55 | 55 | self._ready = False |
67 | 67 | self._findjob = None |
68 | 68 | self.__in_search = False |
69 | 69 | self.__search_fwd = True |
70 | ||
70 | ||
71 | 71 | self._sw = gtk.ScrolledWindow() |
72 | 72 | self._view = widgets._WebView() |
73 | 73 | self._view.load_string(LOADING_HTML, 'text/html', 'utf-8', '/') |
81 | 81 | self._view.connect('button-release-event', self._view_buttonrelease_event_cb) |
82 | 82 | self._view.connect('selection-changed', self._view_selection_changed_cb) |
83 | 83 | self._view.connect_after('populate-popup', self._view_populate_popup_cb) |
84 | ||
84 | ||
85 | 85 | self._sw.add(self._view) |
86 | 86 | self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) |
87 | 87 | self._v_vscrollbar = self._sw.get_vscrollbar() |
93 | 93 | self._scrollbar_change_value_cb) |
94 | 94 | self.pack_start(self._sw, expand = True, fill = True) |
95 | 95 | self.pack_start(self._scrollbar, expand = False, fill = False) |
96 | ||
97 | self._view.set_flags(gtk.CAN_DEFAULT|gtk.CAN_FOCUS) | |
98 | ||
96 | ||
97 | self._view.set_flags(gtk.CAN_DEFAULT|gtk.CAN_FOCUS) | |
98 | ||
99 | 99 | def set_document(self, epubdocumentinstance): |
100 | 100 | ''' |
101 | 101 | Sets document (should be a Epub instance) |
102 | ''' | |
102 | ''' | |
103 | 103 | self._epub = epubdocumentinstance |
104 | 104 | gobject.idle_add(self._paginate) |
105 | 105 | |
116 | 116 | self.__set_zoom(value) |
117 | 117 | else: |
118 | 118 | raise AttributeError, 'unknown property %s' % property.name |
119 | ||
119 | ||
120 | 120 | def get_has_selection(self): |
121 | 121 | ''' |
122 | 122 | Returns True if any part of the content is selected |
123 | ''' | |
123 | ''' | |
124 | 124 | return self.get_property('has-selection') |
125 | ||
125 | ||
126 | 126 | def get_zoom(self): |
127 | 127 | ''' |
128 | 128 | Returns the current zoom level |
129 | ''' | |
129 | ''' | |
130 | 130 | return self.get_property('zoom') |
131 | ||
131 | ||
132 | 132 | def set_zoom(self, value): |
133 | 133 | ''' |
134 | 134 | Sets the current zoom level |
135 | ''' | |
135 | ''' | |
136 | 136 | self.set_property('zoom', value) |
137 | ||
137 | ||
138 | 138 | def zoom_in(self): |
139 | 139 | ''' |
140 | 140 | Zooms in (increases zoom level by 0.1) |
141 | ''' | |
141 | ''' | |
142 | 142 | if self.can_zoom_in(): |
143 | 143 | self.set_zoom(self.get_zoom() + 0.1) |
144 | 144 | return True |
148 | 148 | def zoom_out(self): |
149 | 149 | ''' |
150 | 150 | Zooms out (decreases zoom level by 0.1) |
151 | ''' | |
151 | ''' | |
152 | 152 | if self.can_zoom_out(): |
153 | 153 | self.set_zoom(self.get_zoom() - 0.1) |
154 | 154 | return True |
155 | 155 | else: |
156 | 156 | return False |
157 | ||
157 | ||
158 | 158 | def can_zoom_in(self): |
159 | 159 | ''' |
160 | 160 | Returns True if it is possible to zoom in further |
161 | ''' | |
161 | ''' | |
162 | 162 | if self.zoom < 4: |
163 | 163 | return True |
164 | 164 | else: |
167 | 167 | def can_zoom_out(self): |
168 | 168 | ''' |
169 | 169 | Returns True if it is possible to zoom out further |
170 | ''' | |
170 | ''' | |
171 | 171 | if self.zoom > 0.5: |
172 | 172 | return True |
173 | 173 | else: |
174 | 174 | return False |
175 | ||
175 | ||
176 | 176 | def get_current_page(self): |
177 | 177 | ''' |
178 | 178 | Returns the currently loaded page |
179 | ''' | |
179 | ''' | |
180 | 180 | return self._loaded_page |
181 | ||
181 | ||
182 | 182 | def get_current_file(self): |
183 | 183 | ''' |
184 | 184 | Returns the currently loaded XML file |
185 | ''' | |
185 | ''' | |
186 | 186 | #return self._loaded_filename |
187 | if self._paginator: | |
187 | if self._paginator: | |
188 | 188 | return self._paginator.get_file_for_pageno(self._loaded_page) |
189 | 189 | else: |
190 | 190 | return None |
191 | ||
191 | ||
192 | 192 | def get_pagecount(self): |
193 | 193 | ''' |
194 | 194 | Returns the pagecount of the loaded file |
195 | ''' | |
195 | ''' | |
196 | 196 | return self._pagecount |
197 | ||
197 | ||
198 | 198 | def set_current_page(self, n): |
199 | 199 | ''' |
200 | 200 | Loads page number n |
201 | ''' | |
201 | ''' | |
202 | 202 | if n < 1 or n > self._pagecount: |
203 | 203 | return False |
204 | 204 | self._load_page(n) |
205 | 205 | return True |
206 | ||
206 | ||
207 | 207 | def next_page(self): |
208 | 208 | ''' |
209 | 209 | Loads next page if possible |
210 | 210 | Returns True if transition to next page is possible and done |
211 | ''' | |
211 | ''' | |
212 | 212 | if self._loaded_page == self._pagecount: |
213 | 213 | return False |
214 | 214 | self._load_next_page() |
215 | 215 | return True |
216 | ||
216 | ||
217 | 217 | def previous_page(self): |
218 | 218 | ''' |
219 | 219 | Loads previous page if possible |
220 | 220 | Returns True if transition to previous page is possible and done |
221 | ''' | |
221 | ''' | |
222 | 222 | if self._loaded_page == 1: |
223 | 223 | return False |
224 | 224 | self._load_prev_page() |
228 | 228 | ''' |
229 | 229 | Scrolls through the pages. |
230 | 230 | Scrolling is horizontal if horizontal is set to True |
231 | Valid scrolltypes are: gtk.SCROLL_PAGE_BACKWARD and gtk.SCROLL_PAGE_FORWARD | |
232 | ''' | |
231 | Valid scrolltypes are: gtk.SCROLL_PAGE_BACKWARD and gtk.SCROLL_PAGE_FORWARD | |
232 | ''' | |
233 | 233 | if scrolltype == gtk.SCROLL_PAGE_BACKWARD: |
234 | 234 | self.__going_back = True |
235 | 235 | self.__going_fwd = False |
242 | 242 | self._view.move_cursor(gtk.MOVEMENT_PAGES, 1) |
243 | 243 | else: |
244 | 244 | print ('Got unsupported scrolltype %s' % str(scrolltype)) |
245 | ||
245 | ||
246 | 246 | def copy(self): |
247 | 247 | ''' |
248 | 248 | Copies the current selection to clipboard. |
249 | ''' | |
249 | ''' | |
250 | 250 | self._view.copy_clipboard() |
251 | ||
251 | ||
252 | 252 | def find_next(self): |
253 | 253 | ''' |
254 | 254 | Highlights the next matching item for current search |
255 | ''' | |
255 | ''' | |
256 | 256 | self._view.grab_focus() |
257 | 257 | self._view.grab_default() |
258 | 258 | |
264 | 264 | self.__in_search = True |
265 | 265 | self.__search_fwd = True |
266 | 266 | self._load_file(path) |
267 | ||
267 | ||
268 | 268 | def find_previous(self): |
269 | 269 | ''' |
270 | 270 | Highlights the previous matching item for current search |
271 | ''' | |
271 | ''' | |
272 | 272 | self._view.grab_focus() |
273 | 273 | self._view.grab_default() |
274 | 274 | |
288 | 288 | #self._view.search_text(self._findjob.get_search_text(), \ |
289 | 289 | # self._findjob.get_case_sensitive(), True, False) |
290 | 290 | self.find_next() |
291 | ||
291 | ||
292 | 292 | def __set_zoom(self, value): |
293 | 293 | self._view.set_zoom_level(value) |
294 | 294 | self.zoom = value |
295 | ||
295 | ||
296 | 296 | def __set_has_selection(self, value): |
297 | 297 | if value != self.has_selection: |
298 | 298 | self.has_selection = value |
299 | 299 | self.notify('has-selection') |
300 | ||
300 | ||
301 | 301 | def _view_populate_popup_cb(self, view, menu): |
302 | 302 | menu.destroy() #HACK |
303 | 303 | return |
304 | ||
304 | ||
305 | 305 | def _view_selection_changed_cb(self, view): |
306 | # FIXME: This does not seem to be implemented in | |
306 | # FIXME: This does not seem to be implemented in | |
307 | 307 | # webkitgtk yet |
308 | 308 | print view.has_selection() |
309 | ||
309 | ||
310 | 310 | def _view_buttonrelease_event_cb(self, view, event): |
311 | 311 | # Ugly hack |
312 | 312 | self.__set_has_selection(view.can_copy_clipboard() \ |
313 | 313 | | view.can_cut_clipboard()) |
314 | ||
314 | ||
315 | 315 | def _view_keypress_event_cb(self, view, event): |
316 | 316 | name = gtk.gdk.keyval_name(event.keyval) |
317 | if name == 'Page_Down' or name == 'Down': | |
317 | if name == 'Page_Down' or name == 'Down': | |
318 | 318 | self.__going_back = False |
319 | 319 | self.__going_fwd = True |
320 | 320 | elif name == 'Page_Up' or name == 'Up': |
321 | 321 | self.__going_back = True |
322 | 322 | self.__going_fwd = False |
323 | ||
323 | ||
324 | 324 | self._do_page_transition() |
325 | 325 | |
326 | 326 | def _view_scroll_event_cb(self, view, event): |
331 | 331 | self.__going_back = True |
332 | 332 | self.__going_fwd = False |
333 | 333 | |
334 | self._do_page_transition() | |
335 | ||
334 | self._do_page_transition() | |
335 | ||
336 | 336 | def _do_page_transition(self): |
337 | 337 | if self.__going_fwd: |
338 | 338 | if self._v_vscrollbar.get_value() >= \ |
344 | 344 | if self._v_vscrollbar.get_value() == self._v_vscrollbar.props.adjustment.props.lower: |
345 | 345 | self._load_prev_file() |
346 | 346 | return True |
347 | ||
347 | ||
348 | 348 | return False |
349 | ||
349 | ||
350 | 350 | def _view_load_finished_cb(self, v, frame): |
351 | ||
352 | # Normally the line below would not be required - ugly workaround for | |
351 | ||
352 | # Normally the line below would not be required - ugly workaround for | |
353 | 353 | # possible Webkit bug. See : https://bugs.launchpad.net/bugs/483231 |
354 | 354 | self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) |
355 | ||
355 | ||
356 | 356 | filename = self._view.props.uri.replace('file://', '') |
357 | 357 | if os.path.exists(filename.replace('xhtml', 'xml')): |
358 | 358 | filename = filename.replace('xhtml', 'xml') # Hack for making javascript work |
359 | ||
359 | ||
360 | 360 | filename = filename.split('#')[0] # Get rid of anchors |
361 | ||
361 | ||
362 | 362 | if self._loaded_page < 1 or filename == None: |
363 | 363 | return False |
364 | ||
364 | ||
365 | 365 | self._loaded_filename = filename |
366 | ||
366 | ||
367 | 367 | remfactor = self._paginator.get_remfactor_for_file(filename) |
368 | 368 | pages = self._paginator.get_pagecount_for_file(filename) |
369 | 369 | extra = int(math.ceil(remfactor * self._view.get_page_height()/(pages-remfactor))) |
370 | 370 | if extra > 0: |
371 | 371 | self._view.add_bottom_padding(extra) |
372 | ||
372 | ||
373 | 373 | if self.__in_search: |
374 | 374 | self._view.search_text(self._findjob.get_search_text(), \ |
375 | 375 | self._findjob.get_case_sensitive(), \ |
381 | 381 | self._scroll_page_end() |
382 | 382 | else: |
383 | 383 | self._scroll_page() |
384 | ||
384 | ||
385 | 385 | base_pageno = self._paginator.get_base_pageno_for_file(filename) |
386 | 386 | scrollval = self._v_vscrollbar.get_value() |
387 | 387 | scroll_upper = self._v_vscrollbar.props.adjustment.props.upper |
391 | 391 | else: |
392 | 392 | offset = (scrollval/scroll_upper) * self._paginator.get_pagecount_for_file(filename) |
393 | 393 | pageno = math.floor(base_pageno + offset) |
394 | ||
394 | ||
395 | 395 | if pageno != self._loaded_page: |
396 | 396 | self._on_page_changed(int(pageno)) |
397 | ||
397 | ||
398 | 398 | |
399 | 399 | def _scroll_page_end(self): |
400 | 400 | v_upper = self._v_vscrollbar.props.adjustment.props.upper |
401 | 401 | v_page_size = self._v_vscrollbar.props.adjustment.props.page_size |
402 | 402 | self._v_vscrollbar.set_value(v_upper) |
403 | ||
403 | ||
404 | 404 | def _scroll_page(self): |
405 | 405 | pageno = self._loaded_page |
406 | ||
406 | ||
407 | 407 | v_upper = self._v_vscrollbar.props.adjustment.props.upper |
408 | 408 | v_page_size = self._v_vscrollbar.props.adjustment.props.page_size |
409 | ||
409 | ||
410 | 410 | scrollfactor = self._paginator.get_scrollfactor_pos_for_pageno(pageno) |
411 | 411 | self._v_vscrollbar.set_value((v_upper - v_page_size) * scrollfactor) |
412 | ||
412 | ||
413 | 413 | def _paginate(self): |
414 | 414 | filelist = [] |
415 | 415 | for i in self._epub._navmap.get_flattoc(): |
416 | 416 | filelist.append(os.path.join(self._epub._tempdir, i)) |
417 | ||
417 | ||
418 | 418 | self._paginator = _Paginator(filelist) |
419 | 419 | self._paginator.connect('paginated', self._paginated_cb) |
420 | ||
420 | ||
421 | 421 | def _load_next_page(self): |
422 | 422 | self._load_page(self._loaded_page + 1) |
423 | 423 | |
424 | 424 | def _load_prev_page(self): |
425 | 425 | self._load_page(self._loaded_page - 1) |
426 | ||
426 | ||
427 | 427 | def _v_scrollbar_value_changed_cb(self, scrollbar): |
428 | 428 | if self._loaded_page < 1: |
429 | 429 | return |
456 | 456 | |
457 | 457 | if scrollfactor <= scrollfactor_cur: |
458 | 458 | self._on_page_changed(self._loaded_page - 1) |
459 | ||
459 | ||
460 | 460 | def _on_page_changed(self, pageno): |
461 | 461 | self.__page_changed = True |
462 | 462 | self._loaded_page = pageno |
464 | 464 | self._scrollbar.set_value(pageno) |
465 | 465 | self._scrollbar.handler_unblock(self._scrollbar_change_value_cb_id) |
466 | 466 | self.emit('page-changed') |
467 | ||
467 | ||
468 | 468 | def _load_page(self, pageno): |
469 | 469 | if pageno > self._pagecount or pageno < 1: |
470 | 470 | #TODO: Cause an exception |
482 | 482 | self._view.open(filename) |
483 | 483 | else: |
484 | 484 | self._scroll_page() |
485 | ||
485 | ||
486 | 486 | def _load_next_file(self): |
487 | 487 | if self._loaded_page == self._pagecount: |
488 | 488 | return |
492 | 492 | pageno += 1 |
493 | 493 | if self._paginator.get_file_for_pageno(pageno) != cur_file: |
494 | 494 | break |
495 | ||
495 | ||
496 | 496 | self._load_page(pageno) |
497 | 497 | |
498 | 498 | def _load_file(self, path): |
512 | 512 | pageno -= 1 |
513 | 513 | if self._paginator.get_file_for_pageno(pageno) != cur_file: |
514 | 514 | break |
515 | ||
515 | ||
516 | 516 | self._load_page(pageno) |
517 | 517 | |
518 | 518 | def _scrollbar_change_value_cb(self, range, scrolltype, value): |
525 | 525 | self.__going_fwd = False |
526 | 526 | self.__going_back = True |
527 | 527 | if not self._do_page_transition(): |
528 | self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, -1) | |
528 | self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, -1) | |
529 | 529 | elif scrolltype == gtk.SCROLL_JUMP or \ |
530 | 530 | scrolltype == gtk.SCROLL_PAGE_FORWARD or \ |
531 | 531 | scrolltype == gtk.SCROLL_PAGE_BACKWARD: |
535 | 535 | self._load_page(round(value)) |
536 | 536 | else: |
537 | 537 | print 'Warning: unknown scrolltype %s with value %f' % (str(scrolltype), value) |
538 | ||
538 | ||
539 | 539 | self._scrollbar.set_value(self._loaded_page) #FIXME: This should not be needed here |
540 | ||
540 | ||
541 | 541 | if self.__page_changed == True: |
542 | 542 | self.__page_changed = False |
543 | 543 | return False |
544 | 544 | else: |
545 | 545 | return True |
546 | ||
546 | ||
547 | 547 | def _paginated_cb(self, object): |
548 | 548 | self._ready = True |
549 | ||
549 | ||
550 | 550 | self._pagecount = self._paginator.get_total_pagecount() |
551 | 551 | self._scrollbar.set_range(1.0, self._pagecount - 1.0) |
552 | 552 | self._scrollbar.set_increments(1.0, 1.0) |
554 | 554 | self._view.grab_default() |
555 | 555 | if self._loaded_page < 1: |
556 | 556 | self._load_page(1) |
557 | ||
558 | ||
557 | ||
558 | ||
559 | 559 | |
560 | 560 | def _destroy_cb(self, widget): |
561 | 561 | self._epub.close() |
56 | 56 | if self._searchfile(f): |
57 | 57 | self.obj._matchfilelist.append(entry) |
58 | 58 | f.close() |
59 | ||
59 | ||
60 | 60 | gtk.gdk.threads_enter() |
61 | self.obj._finished = True | |
61 | self.obj._finished = True | |
62 | 62 | self.obj.emit('updated') |
63 | 63 | gtk.gdk.threads_leave() |
64 | ||
64 | ||
65 | 65 | return False |
66 | ||
66 | ||
67 | 67 | def _searchfile(self, fileobj): |
68 | 68 | soup = BeautifulSoup.BeautifulSoup(fileobj) |
69 | 69 | body = soup.find('body') |
70 | 70 | tags = body.findChildren(True) |
71 | 71 | for tag in tags: |
72 | if not tag.string is None: | |
72 | if not tag.string is None: | |
73 | 73 | if tag.string.find(self.obj._text) > -1: |
74 | 74 | return True |
75 | ||
75 | ||
76 | 76 | return False |
77 | 77 | |
78 | 78 | def run (self): |
79 | 79 | self._start_search() |
80 | ||
80 | ||
81 | 81 | def stop(self): |
82 | 82 | self.stopthread.set() |
83 | 83 | |
88 | 88 | 'paginated': (gobject.SIGNAL_RUN_FIRST, |
89 | 89 | gobject.TYPE_NONE, |
90 | 90 | ([])) |
91 | } | |
91 | } | |
92 | 92 | def __init__(self, filelist): |
93 | 93 | gobject.GObject.__init__(self) |
94 | ||
94 | ||
95 | 95 | self._filelist = filelist |
96 | 96 | self._filedict = {} |
97 | 97 | self._pagemap = {} |
98 | ||
98 | ||
99 | 99 | self._bookheight = 0 |
100 | 100 | self._count = 0 |
101 | 101 | self._pagecount = 0 |
102 | ||
102 | ||
103 | 103 | self._screen = gtk.gdk.screen_get_default() |
104 | 104 | self._old_fontoptions = self._screen.get_font_options() |
105 | 105 | options = cairo.FontOptions() |
108 | 108 | options.set_subpixel_order(cairo.SUBPIXEL_ORDER_DEFAULT) |
109 | 109 | options.set_hint_metrics(cairo.HINT_METRICS_DEFAULT) |
110 | 110 | self._screen.set_font_options(options) |
111 | ||
111 | ||
112 | 112 | self._temp_win = gtk.Window() |
113 | 113 | self._temp_view = widgets._WebView() |
114 | 114 | |
123 | 123 | settings.props.default_font_size = 12 |
124 | 124 | settings.props.default_monospace_font_size = 10 |
125 | 125 | settings.props.default_encoding = 'utf-8' |
126 | ||
126 | ||
127 | 127 | sw = gtk.ScrolledWindow() |
128 | 128 | sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) |
129 | 129 | self._dpi = 96 |
131 | 131 | sw.add(self._temp_view) |
132 | 132 | self._temp_win.add(sw) |
133 | 133 | self._temp_view.connect('load-finished', self._page_load_finished_cb) |
134 | ||
134 | ||
135 | 135 | self._temp_win.show_all() |
136 | 136 | self._temp_win.unmap() |
137 | ||
137 | ||
138 | 138 | self._temp_view.open(self._filelist[self._count]) |
139 | ||
139 | ||
140 | 140 | def _page_load_finished_cb(self, v, frame): |
141 | 141 | f = v.get_main_frame() |
142 | 142 | pageheight = v.get_page_height() |
143 | ||
143 | ||
144 | 144 | if pageheight <= _mm_to_pixel(PAGE_HEIGHT, self._dpi): |
145 | 145 | pages = 1 |
146 | 146 | else: |
151 | 151 | else: |
152 | 152 | pagelen = 1/pages |
153 | 153 | self._pagemap[float(self._pagecount + i)] = (f.props.uri, (i-1)/math.ceil(pages), pagelen) |
154 | ||
154 | ||
155 | 155 | self._pagecount += math.ceil(pages) |
156 | 156 | self._filedict[f.props.uri.replace('file://', '')] = (math.ceil(pages), math.ceil(pages) - pages) |
157 | 157 | self._bookheight += pageheight |
158 | ||
158 | ||
159 | 159 | if self._count+1 >= len(self._filelist): |
160 | 160 | self._temp_win.destroy() |
161 | 161 | self._screen.set_font_options(self._old_fontoptions) |
163 | 163 | else: |
164 | 164 | self._count += 1 |
165 | 165 | self._temp_view.open(self._filelist[self._count]) |
166 | ||
167 | ||
166 | ||
167 | ||
168 | 168 | def get_file_for_pageno(self, pageno): |
169 | 169 | ''' |
170 | 170 | Returns the file in which pageno occurs |
171 | ''' | |
171 | ''' | |
172 | 172 | return self._pagemap[pageno][0] |
173 | ||
173 | ||
174 | 174 | def get_scrollfactor_pos_for_pageno(self, pageno): |
175 | 175 | ''' |
176 | 176 | Returns the position scrollfactor (fraction) for pageno |
177 | ''' | |
177 | ''' | |
178 | 178 | return self._pagemap[pageno][1] |
179 | 179 | |
180 | 180 | def get_scrollfactor_len_for_pageno(self, pageno): |
181 | 181 | ''' |
182 | 182 | Returns the length scrollfactor (fraction) for pageno |
183 | ''' | |
183 | ''' | |
184 | 184 | return self._pagemap[pageno][2] |
185 | ||
185 | ||
186 | 186 | def get_pagecount_for_file(self, filename): |
187 | 187 | ''' |
188 | 188 | Returns the number of pages in file |
189 | ''' | |
189 | ''' | |
190 | 190 | return self._filedict[filename][0] |
191 | 191 | |
192 | 192 | def get_base_pageno_for_file(self, filename): |
193 | 193 | ''' |
194 | 194 | Returns the pageno which begins in filename |
195 | ''' | |
195 | ''' | |
196 | 196 | for key in self._pagemap.keys(): |
197 | 197 | if self._pagemap[key][0].replace('file://', '') == filename: |
198 | 198 | return key |
199 | ||
199 | ||
200 | 200 | return None |
201 | 201 | |
202 | 202 | def get_remfactor_for_file(self, filename): |
203 | 203 | ''' |
204 | 204 | Returns the remainder factor (1 - fraction length of last page in file) |
205 | ''' | |
205 | ''' | |
206 | 206 | return self._filedict[filename][1] |
207 | ||
207 | ||
208 | 208 | def get_total_pagecount(self): |
209 | 209 | ''' |
210 | 210 | Returns the total pagecount for the Epub file |
214 | 214 | def get_total_height(self): |
215 | 215 | ''' |
216 | 216 | Returns the total height of the Epub in pixels |
217 | ''' | |
217 | ''' | |
218 | 218 | return self._bookheight |
219 | 219 | |
220 | 220 | |
227 | 227 | def __init__(self, document, start_page, n_pages, text, case_sensitive=False): |
228 | 228 | gobject.GObject.__init__(self) |
229 | 229 | gtk.gdk.threads_init() |
230 | ||
230 | ||
231 | 231 | self._finished = False |
232 | 232 | self._document = document |
233 | 233 | self._start_page = start_page |
238 | 238 | self._matchfilelist = [] |
239 | 239 | self._current_file_index = 0 |
240 | 240 | self.threads = [] |
241 | ||
241 | ||
242 | 242 | s_thread = SearchThread(self) |
243 | 243 | self.threads.append(s_thread) |
244 | 244 | s_thread.start() |
245 | ||
245 | ||
246 | 246 | def cancel(self): |
247 | 247 | ''' |
248 | 248 | Cancels the search job |
249 | ''' | |
249 | ''' | |
250 | 250 | for s_thread in self.threads: |
251 | 251 | s_thread.stop() |
252 | ||
252 | ||
253 | 253 | def is_finished(self): |
254 | 254 | ''' |
255 | 255 | Returns True if the entire search job has been finished |
256 | ''' | |
256 | ''' | |
257 | 257 | return self._finished |
258 | ||
258 | ||
259 | 259 | def get_next_file(self): |
260 | 260 | ''' |
261 | 261 | Returns the next file which has the search pattern |
262 | ''' | |
262 | ''' | |
263 | 263 | self._current_file_index += 1 |
264 | 264 | try: |
265 | 265 | path = self._matchfilelist[self._current_file_index] |
266 | 266 | except IndexError: |
267 | 267 | self._current_file_index = 0 |
268 | 268 | path = self._matchfilelist[self._current_file_index] |
269 | ||
269 | ||
270 | 270 | return path |
271 | 271 | |
272 | 272 | def get_prev_file(self): |
273 | 273 | ''' |
274 | 274 | Returns the previous file which has the search pattern |
275 | ''' | |
275 | ''' | |
276 | 276 | self._current_file_index -= 1 |
277 | 277 | try: |
278 | 278 | path = self._matchfilelist[self._current_file_index] |
279 | 279 | except IndexError: |
280 | 280 | self._current_file_index = -1 |
281 | 281 | path = self._matchfilelist[self._current_file_index] |
282 | ||
282 | ||
283 | 283 | return path |
284 | 284 | |
285 | 285 | def get_search_text(self): |
286 | 286 | ''' |
287 | 287 | Returns the search text |
288 | ''' | |
288 | ''' | |
289 | 289 | return self._text |
290 | ||
290 | ||
291 | 291 | def get_case_sensitive(self): |
292 | 292 | ''' |
293 | 293 | Returns True if the search is case-sensitive |
294 | ''' | |
294 | ''' | |
295 | 295 | return self._case_sensitive |
4 | 4 | def __init__(self, label, contentsrc, children = []): |
5 | 5 | self._label = label |
6 | 6 | self._contentsrc = contentsrc |
7 | self._children = children | |
8 | ||
7 | self._children = children | |
8 | ||
9 | 9 | def get_label(self): |
10 | 10 | return self._label |
11 | ||
11 | ||
12 | 12 | def get_contentsrc(self): |
13 | 13 | return self._contentsrc |
14 | ||
14 | ||
15 | 15 | def get_children(self): |
16 | 16 | return self._children |
17 | 17 | |
24 | 24 | self._root = self._tree.getroot() |
25 | 25 | self._gtktreestore = gtk.TreeStore(str, str) |
26 | 26 | self._flattoc = [] |
27 | ||
27 | ||
28 | 28 | self._populate_flattoc() |
29 | 29 | self._populate_toc() |
30 | ||
30 | ||
31 | 31 | def _populate_flattoc(self): |
32 | 32 | tree = etree.parse(self._opffile) |
33 | 33 | root = tree.getroot() |
34 | ||
34 | ||
35 | 35 | itemmap = {} |
36 | 36 | manifest = root.find('.//{http://www.idpf.org/2007/opf}manifest') |
37 | 37 | for element in manifest.iterfind('{http://www.idpf.org/2007/opf}item'): |
38 | 38 | itemmap[element.get('id')] = element |
39 | ||
40 | spine = root.find('.//{http://www.idpf.org/2007/opf}spine') | |
39 | ||
40 | spine = root.find('.//{http://www.idpf.org/2007/opf}spine') | |
41 | 41 | for element in spine.iterfind('{http://www.idpf.org/2007/opf}itemref'): |
42 | 42 | idref = element.get('idref') |
43 | 43 | href = itemmap[idref].get('href') |
44 | 44 | self._flattoc.append(self._basepath + href) |
45 | ||
45 | ||
46 | 46 | self._opffile.close() |
47 | ||
47 | ||
48 | 48 | def _populate_toc(self): |
49 | navmap = self._root.find('{http://www.daisy.org/z3986/2005/ncx/}navMap') | |
49 | navmap = self._root.find('{http://www.daisy.org/z3986/2005/ncx/}navMap') | |
50 | 50 | for navpoint in navmap.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint'): |
51 | 51 | self._process_navpoint(navpoint) |
52 | ||
52 | ||
53 | 53 | def _gettitle(self, navpoint): |
54 | 54 | text = navpoint.find('./{http://www.daisy.org/z3986/2005/ncx/}navLabel/{http://www.daisy.org/z3986/2005/ncx/}text') |
55 | 55 | return text.text |
61 | 61 | def _process_navpoint(self, navpoint, parent = None): |
62 | 62 | title = self._gettitle(navpoint) |
63 | 63 | content = self._getcontent(navpoint) |
64 | ||
64 | ||
65 | 65 | #print title, content |
66 | ||
66 | ||
67 | 67 | iter = self._gtktreestore.append(parent, [title, content]) |
68 | 68 | #self._flattoc.append((title, content)) |
69 | ||
69 | ||
70 | 70 | childnavpointlist = list(navpoint.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint')) |
71 | ||
71 | ||
72 | 72 | if len(childnavpointlist): |
73 | 73 | for childnavpoint in childnavpointlist: |
74 | self._process_navpoint(childnavpoint, parent = iter) | |
75 | else: | |
74 | self._process_navpoint(childnavpoint, parent = iter) | |
75 | else: | |
76 | 76 | return |
77 | ||
77 | ||
78 | 78 | def get_gtktreestore(self): |
79 | 79 | ''' |
80 | 80 | Returns a GtkTreeModel representation of the |
81 | 81 | Epub table of contents |
82 | ''' | |
82 | ''' | |
83 | 83 | return self._gtktreestore |
84 | ||
84 | ||
85 | 85 | def get_flattoc(self): |
86 | 86 | ''' |
87 | 87 | Returns a flat (linear) list of files to be |
88 | 88 | rendered. |
89 | ''' | |
89 | ''' | |
90 | 90 | return self._flattoc |
91 | ||
92 | #t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx')⏎ | |
91 | ||
92 | #t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx') |
4 | 4 | class _WebView(webkit.WebView): |
5 | 5 | def __init__(self): |
6 | 6 | webkit.WebView.__init__(self) |
7 | ||
7 | ||
8 | 8 | def get_page_height(self): |
9 | 9 | ''' |
10 | 10 | Gets height (in pixels) of loaded (X)HTML page. |
11 | 11 | This is done via javascript at the moment |
12 | ''' | |
12 | ''' | |
13 | 13 | #TODO: Need to check status of page load |
14 | 14 | js = 'oldtitle=document.title;document.title=Math.max(document.body.scrollHeight, document.body.offsetHeight,document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);' |
15 | 15 | self.execute_script(js) |
19 | 19 | if ret is None: |
20 | 20 | return 0 |
21 | 21 | return int(ret) |
22 | ||
22 | ||
23 | 23 | def add_bottom_padding(self, incr): |
24 | 24 | ''' |
25 | 25 | Adds incr pixels of padding to the end of the loaded (X)HTML page. |
26 | 26 | This is done via javascript at the moment |
27 | ''' | |
27 | ''' | |
28 | 28 | js = ('var newdiv = document.createElement("div");newdiv.style.height = "%dpx";document.body.appendChild(newdiv);' % incr) |
29 | 29 | self.execute_script(js) |
30 | ||
30 |
71 | 71 | return float(xft_dpi / 1024) |
72 | 72 | |
73 | 73 | def get_md5(filename): #FIXME: Should be moved somewhere else |
74 | filename = filename.replace('file://', '') #XXX: hack | |
74 | filename = filename.replace('file://', '') #XXX: hack | |
75 | 75 | fh = open(filename) |
76 | 76 | digest = md5.new() |
77 | 77 | while 1: |
132 | 132 | if hasattr(evince, 'evince_embed_init'): |
133 | 133 | # if we use evince-2.24 |
134 | 134 | evince.evince_embed_init() |
135 | ||
135 | ||
136 | 136 | self._epub = False |
137 | 137 | self._document = None |
138 | 138 | self._fileserver = None |
143 | 143 | self.connect('window-state-event', self._window_state_event_cb) |
144 | 144 | |
145 | 145 | _logger.debug('Starting Read...') |
146 | ||
146 | ||
147 | 147 | self._view = None |
148 | ||
148 | ||
149 | 149 | self._sidebar = Sidebar() |
150 | 150 | self._sidebar.show() |
151 | 151 | |
228 | 228 | spacer.show() |
229 | 229 | |
230 | 230 | bookmark_item = gtk.ToolItem() |
231 | self._bookmarker = self._create_bookmarker() | |
231 | self._bookmarker = self._create_bookmarker() | |
232 | 232 | self._bookmarker_toggle_handler_id = self._bookmarker.connect( \ |
233 | 233 | 'toggled', self.__bookmarker_toggled_cb) |
234 | 234 | bookmark_item.add(self._bookmarker) |
330 | 330 | back.props.sensitive = False |
331 | 331 | palette = back.get_palette() |
332 | 332 | previous_page = MenuItem(text_label= _("Previous page")) |
333 | palette.menu.append(previous_page) | |
334 | previous_page.show_all() | |
333 | palette.menu.append(previous_page) | |
334 | previous_page.show_all() | |
335 | 335 | previous_bookmark = MenuItem(text_label= _("Previous bookmark")) |
336 | palette.menu.append(previous_bookmark) | |
336 | palette.menu.append(previous_bookmark) | |
337 | 337 | previous_bookmark.show_all() |
338 | 338 | back.connect('clicked', self.__go_back_cb) |
339 | 339 | previous_page.connect('activate', self.__go_back_page_cb) |
346 | 346 | forward.props.sensitive = False |
347 | 347 | palette = forward.get_palette() |
348 | 348 | next_page = MenuItem(text_label= _("Next page")) |
349 | palette.menu.append(next_page) | |
350 | next_page.show_all() | |
349 | palette.menu.append(next_page) | |
350 | next_page.show_all() | |
351 | 351 | next_bookmark = MenuItem(text_label= _("Next bookmark")) |
352 | palette.menu.append(next_bookmark) | |
352 | palette.menu.append(next_bookmark) | |
353 | 353 | next_bookmark.show_all() |
354 | 354 | forward.connect('clicked', self.__go_forward_cb) |
355 | 355 | next_page.connect('activate', self.__go_forward_page_cb) |
372 | 372 | |
373 | 373 | label_attributes = pango.AttrList() |
374 | 374 | label_attributes.insert(pango.AttrSize(14000, 0, -1)) |
375 | label_attributes.insert(pango.AttrForeground(65535, 65535, | |
375 | label_attributes.insert(pango.AttrForeground(65535, 65535, | |
376 | 376 | 65535, 0, -1)) |
377 | 377 | total_page_label.set_attributes(label_attributes) |
378 | 378 | |
379 | 379 | total_page_label.set_text(' / 0') |
380 | 380 | return total_page_label |
381 | ||
381 | ||
382 | 382 | def _create_navigator(self): |
383 | 383 | navigator = gtk.ComboBox() |
384 | 384 | navigator.set_add_tearoffs(True) |
391 | 391 | def _create_bookmarker(self): |
392 | 392 | bookmarker = ToggleToolButton('emblem-favorite') |
393 | 393 | return bookmarker |
394 | ||
394 | ||
395 | 395 | def __num_page_entry_insert_text_cb(self, entry, text, length, position): |
396 | 396 | if not re.match('[0-9]', text): |
397 | 397 | entry.emit_stop_by_name('insert-text') |
411 | 411 | |
412 | 412 | self._document.get_page_cache().set_current_page(page) |
413 | 413 | entry.props.text = str(page + 1) |
414 | ||
414 | ||
415 | 415 | def __go_back_cb(self, button): |
416 | 416 | self._view.scroll(gtk.SCROLL_PAGE_BACKWARD, False) |
417 | 417 | |
420 | 420 | |
421 | 421 | def __go_back_page_cb(self, button): |
422 | 422 | self._view.previous_page() |
423 | ||
423 | ||
424 | 424 | def __go_forward_page_cb(self, button): |
425 | 425 | self._view.next_page() |
426 | 426 | |
427 | 427 | def __prev_bookmark_activate_cb(self, menuitem): |
428 | 428 | page = self._document.get_page_cache().get_current_page() |
429 | 429 | bookmarkmanager = self._sidebar.get_bookmarkmanager() |
430 | ||
430 | ||
431 | 431 | prev_bookmark = bookmarkmanager.get_prev_bookmark_for_page(page) |
432 | 432 | if prev_bookmark is not None: |
433 | 433 | self._document.get_page_cache().set_current_page(prev_bookmark.page_no) |
435 | 435 | def __next_bookmark_activate_cb(self, menuitem): |
436 | 436 | page = self._document.get_page_cache().get_current_page() |
437 | 437 | bookmarkmanager = self._sidebar.get_bookmarkmanager() |
438 | ||
438 | ||
439 | 439 | next_bookmark = bookmarkmanager.get_next_bookmark_for_page(page) |
440 | 440 | if next_bookmark is not None: |
441 | 441 | self._document.get_page_cache().set_current_page(next_bookmark.page_no) |
445 | 445 | if self._bookmarker.props.active: |
446 | 446 | self._sidebar.add_bookmark(page) |
447 | 447 | else: |
448 | self._sidebar.del_bookmark(page) | |
448 | self._sidebar.del_bookmark(page) | |
449 | 449 | |
450 | 450 | def __page_changed_cb(self, page, proxy = None): |
451 | 451 | self._update_nav_buttons() |
452 | 452 | if hasattr(self._document, 'has_document_links'): |
453 | 453 | if self._document.has_document_links(): |
454 | 454 | self._toc_select_active_page() |
455 | ||
455 | ||
456 | 456 | self._sidebar.update_for_page(self._document.get_page_cache().get_current_page()) |
457 | 457 | |
458 | 458 | self._bookmarker.handler_block(self._bookmarker_toggle_handler_id) |
459 | 459 | self._bookmarker.props.active = self._sidebar.is_showing_local_bookmark() |
460 | 460 | self._bookmarker.handler_unblock(self._bookmarker_toggle_handler_id) |
461 | ||
461 | ||
462 | 462 | def _update_nav_buttons(self): |
463 | 463 | current_page = self._document.get_page_cache().get_current_page() |
464 | 464 | self._back_button.props.sensitive = current_page > 0 |
465 | 465 | self._forward_button.props.sensitive = \ |
466 | 466 | current_page < self._document.get_n_pages() - 1 |
467 | ||
467 | ||
468 | 468 | self._num_page_entry.props.text = str(current_page + 1) |
469 | 469 | self._total_page_label.props.label = \ |
470 | 470 | ' / ' + str(self._document.get_n_pages()) |
509 | 509 | |
510 | 510 | def _toc_select_active_page(self): |
511 | 511 | iter = self._navigator.get_active_iter() |
512 | ||
512 | ||
513 | 513 | current_link = self._toc_model.get(iter, 1)[0] |
514 | 514 | current_page = self._document.get_page_cache().get_current_page() |
515 | 515 | |
533 | 533 | """ |
534 | 534 | if not self._want_document: |
535 | 535 | return |
536 | chooser = ObjectChooser(_('Choose document'), self, | |
537 | gtk.DIALOG_MODAL | | |
536 | chooser = ObjectChooser(_('Choose document'), self, | |
537 | gtk.DIALOG_MODAL | | |
538 | 538 | gtk.DIALOG_DESTROY_WITH_PARENT, |
539 | 539 | what_filter=mime.GENERIC_TYPE_TEXT) |
540 | 540 | try: |
541 | 541 | result = chooser.run() |
542 | 542 | if result == gtk.RESPONSE_ACCEPT: |
543 | logging.debug('ObjectChooser: %r' % | |
543 | logging.debug('ObjectChooser: %r' % | |
544 | 544 | chooser.get_selected_object()) |
545 | 545 | jobject = chooser.get_selected_object() |
546 | 546 | if jobject and jobject.file_path: |
599 | 599 | |
600 | 600 | def write_file(self, file_path): |
601 | 601 | """Write into datastore for Keep. |
602 | ||
602 | ||
603 | 603 | The document is saved by hardlinking from the temporary file we |
604 | 604 | keep around instead of "saving". |
605 | 605 | |
606 | 606 | The metadata is updated, including current page, view settings, |
607 | 607 | search text. |
608 | ||
608 | ||
609 | 609 | """ |
610 | 610 | if self._tempfile is None: |
611 | 611 | # Workaround for closing Read with no document loaded |
649 | 649 | |
650 | 650 | def can_close(self): |
651 | 651 | """Prepare to cleanup on closing. |
652 | ||
652 | ||
653 | 653 | Called from self.close() |
654 | 654 | """ |
655 | 655 | self._close_requested = True |
680 | 680 | # FIXME: Draw a progress bar |
681 | 681 | if self._download_content_length > 0: |
682 | 682 | _logger.debug("Downloaded %u of %u bytes from tube %u...", |
683 | bytes_downloaded, self._download_content_length, | |
683 | bytes_downloaded, self._download_content_length, | |
684 | 684 | tube_id) |
685 | 685 | else: |
686 | 686 | _logger.debug("Downloaded %u bytes from tube %u...", |
785 | 785 | """Load the specified document and set up the UI. |
786 | 786 | |
787 | 787 | filepath -- string starting with file:// |
788 | ||
788 | ||
789 | 789 | """ |
790 | 790 | mimetype = mime.get_for_file(filepath) |
791 | 791 | if mimetype == 'application/epub+zip': |
815 | 815 | |
816 | 816 | self._update_nav_buttons() |
817 | 817 | self._update_toc() |
818 | ||
818 | ||
819 | 819 | page_cache = self._document.get_page_cache() |
820 | 820 | page_cache.connect('page-changed', self.__page_changed_cb) |
821 | 821 |
25 | 25 | self.nick = data[4] |
26 | 26 | self.color = data[5] |
27 | 27 | self.local = data[6] |
28 | ||
28 | ||
29 | 29 | def belongstopage(self, page_no): |
30 | return self.page_no == page_no | |
31 | ||
30 | return self.page_no == page_no | |
31 | ||
32 | 32 | def is_local(self): |
33 | 33 | return bool(self.local) |
34 | 34 | |
42 | 42 | def get_note_body(self): |
43 | 43 | if self.content == '' or self.content is None: |
44 | 44 | return '' |
45 | ||
45 | ||
46 | 46 | note = cjson.decode(self.content) |
47 | 47 | return note['body'] |
48 | ||
49 | 48 | |
49 |
50 | 50 | #Situation 2: DB is outdated |
51 | 51 | if not os.path.exists(dbpath) and os.path.exists(olddbpath): |
52 | 52 | shutil.copy(olddbpath, dbpath) |
53 | ||
53 | ||
54 | 54 | conn = sqlite3.connect(dbpath) |
55 | 55 | conn.execute("CREATE TABLE temp_bookmarks AS SELECT md5, page, title 'content', timestamp, user, color, local FROM bookmarks") |
56 | 56 | conn.execute("ALTER TABLE bookmarks RENAME TO bookmarks_old") |
75 | 75 | self._conn = sqlite3.connect(dbpath) |
76 | 76 | self._conn.text_factory = lambda x: unicode(x, "utf-8", "ignore") |
77 | 77 | |
78 | ||
78 | ||
79 | 79 | self._bookmarks = [] |
80 | 80 | self._populate_bookmarks() |
81 | ||
81 | ||
82 | 82 | def add_bookmark(self, page, content, local=1): |
83 | # locale = 0 means that this is a bookmark originally | |
83 | # locale = 0 means that this is a bookmark originally | |
84 | 84 | # created by the person who originally shared the file |
85 | 85 | timestamp = time.time() |
86 | 86 | client = gconf.client_get_default() |
90 | 90 | t = (self._filehash, page, content, timestamp, user, color, local) |
91 | 91 | self._conn.execute('insert into bookmarks values (?, ?, ?, ?, ?, ?, ?)', t) |
92 | 92 | self._conn.commit() |
93 | ||
93 | ||
94 | 94 | self._resync_bookmark_cache() |
95 | ||
95 | ||
96 | 96 | def del_bookmark(self, page): |
97 | 97 | client = gconf.client_get_default() |
98 | 98 | user = client.get_string("/desktop/sugar/user/nick") |
99 | 99 | |
100 | 100 | # We delete only the locally made bookmark |
101 | ||
101 | ||
102 | 102 | t = (self._filehash, page, user) |
103 | 103 | self._conn.execute('delete from bookmarks where md5=? and page=? and user=?', t) |
104 | 104 | self._conn.commit() |
105 | ||
105 | ||
106 | 106 | self._resync_bookmark_cache() |
107 | 107 | |
108 | 108 | def _populate_bookmarks(self): |
111 | 111 | |
112 | 112 | for row in rows: |
113 | 113 | self._bookmarks.append(Bookmark(row)) |
114 | ||
114 | ||
115 | 115 | def get_bookmarks_for_page(self, page): |
116 | 116 | bookmarks = [] |
117 | 117 | for bookmark in self._bookmarks: |
118 | 118 | if bookmark.belongstopage(page): |
119 | 119 | bookmarks.append(bookmark) |
120 | ||
120 | ||
121 | 121 | return bookmarks |
122 | ||
122 | ||
123 | 123 | def _resync_bookmark_cache(self): |
124 | 124 | # To be called when a new bookmark has been added/removed |
125 | 125 | self._bookmarks = [] |
129 | 129 | def get_prev_bookmark_for_page(self, page, wrap = True): |
130 | 130 | if not len(self._bookmarks): |
131 | 131 | return None |
132 | ||
132 | ||
133 | 133 | if page <= self._bookmarks[0].page_no and wrap: |
134 | 134 | return self._bookmarks[-1] |
135 | 135 | else: |
137 | 137 | for bookmark in self._bookmarks: |
138 | 138 | if bookmark.belongstopage(i): |
139 | 139 | return bookmark |
140 | ||
141 | return None | |
140 | ||
141 | return None | |
142 | 142 | |
143 | 143 | |
144 | 144 | def get_next_bookmark_for_page(self, page, wrap = True): |
145 | 145 | if not len(self._bookmarks): |
146 | 146 | return None |
147 | ||
147 | ||
148 | 148 | if page >= self._bookmarks[-1].page_no and wrap: |
149 | 149 | return self._bookmarks[0] |
150 | 150 | else: |
152 | 152 | for bookmark in self._bookmarks: |
153 | 153 | if bookmark.belongstopage(i): |
154 | 154 | return bookmark |
155 | ||
156 | return None | |
155 | ||
156 | return None |
2 | 2 | # Stolen from the PyGTK demo module by Maik Hertha <maik.hertha@berlin.de> |
3 | 3 | |
4 | 4 | import gtk |
5 | import gobject | |
5 | import gobject | |
6 | 6 | |
7 | 7 | from sugar.graphics import style |
8 | 8 | from sugar.graphics.toolbutton import ToolButton |
55 | 55 | accept.connect('clicked', self.accept_clicked_cb) |
56 | 56 | accept.show() |
57 | 57 | self.toolbar.insert(accept, -1) |
58 | ||
58 | ||
59 | 59 | _vbox.pack_start(self.toolbar, expand=False) |
60 | 60 | self.toolbar.show() |
61 | 61 | |
164 | 164 | def accept_clicked_cb(self, widget): |
165 | 165 | title = self._title_entry.get_text() |
166 | 166 | details = self._content_entry.get_buffer().props.text |
167 | content = {'title' : unicode(title), 'body' : unicode(details)} | |
167 | content = {'title' : unicode(title), 'body' : unicode(details)} | |
168 | 168 | self._sidebarinstance._real_add_bookmark(self._page, cjson.encode(content)) |
169 | 169 | self.destroy() |
170 | 170 | |
175 | 175 | def accept_clicked_cb(self, widget): |
176 | 176 | title = self._title_entry.get_text() |
177 | 177 | details = self._content_entry.get_buffer().props.text |
178 | content = {'title' : unicode(title), 'body' : unicode(details)} | |
178 | content = {'title' : unicode(title), 'body' : unicode(details)} | |
179 | 179 | self._sidebarinstance.del_bookmark(self._page) |
180 | 180 | self._sidebarinstance._real_add_bookmark(self._page, cjson.encode(content)) |
181 | 181 | self.destroy() |
48 | 48 | |
49 | 49 | self._box.show() |
50 | 50 | self.show() |
51 | ||
51 | ||
52 | 52 | self._bookmark_icon = None |
53 | 53 | self._bookmark_manager = None |
54 | 54 | self._is_showing_local_bookmark = False |
73 | 73 | |
74 | 74 | if bookmark.is_local(): |
75 | 75 | self._is_showing_local_bookmark = True |
76 | ||
76 | ||
77 | 77 | def __bookmark_icon_query_tooltip_cb(self, widget, x, y, keyboard_mode, tip, bookmark): |
78 | 78 | tooltip_header = bookmark.get_note_title() |
79 | 79 | tooltip_body = bookmark.get_note_body() |
111 | 111 | tip.set_custom(vbox) |
112 | 112 | |
113 | 113 | return True |
114 | ||
114 | ||
115 | 115 | def __event_cb(self, widget, event, bookmark): |
116 | 116 | if event.type == gtk.gdk.BUTTON_PRESS and \ |
117 | 117 | self._bookmark_icon is not None: |
135 | 135 | |
136 | 136 | self._bookmark_icon.hide() #XXX: Is this needed?? |
137 | 137 | self._bookmark_icon.destroy() |
138 | ||
138 | ||
139 | 139 | self._bookmark_icon = None |
140 | ||
140 | ||
141 | 141 | self._is_showing_local_bookmark = False |
142 | ||
142 | ||
143 | 143 | def set_bookmarkmanager(self, filehash): |
144 | 144 | self._bookmark_manager = BookmarkManager(filehash) |
145 | ||
145 | ||
146 | 146 | def get_bookmarkmanager(self): |
147 | 147 | return (self._bookmark_manager) |
148 | ||
148 | ||
149 | 149 | def update_for_page(self, page): |
150 | 150 | self._clear_bookmarks() |
151 | 151 | if self._bookmark_manager is None: |
152 | 152 | return |
153 | ||
153 | ||
154 | 154 | bookmarks = self._bookmark_manager.get_bookmarks_for_page(page) |
155 | ||
155 | ||
156 | 156 | for bookmark in bookmarks: |
157 | 157 | self._add_bookmark_icon(bookmark) |
158 | ||
158 | ||
159 | 159 | def add_bookmark(self, page): |
160 | 160 | bookmark_title = (_("%s's bookmark") % profile.get_nick_name()) |
161 | 161 | bookmark_content = (_("Bookmark for page %d") % page) |
169 | 169 | def _real_add_bookmark(self, page, content): |
170 | 170 | self._bookmark_manager.add_bookmark(page, unicode(content)) |
171 | 171 | self.update_for_page(page) |
172 | ||
172 | ||
173 | 173 | def del_bookmark(self, page): |
174 | 174 | self._bookmark_manager.del_bookmark(page) |
175 | 175 | self.update_for_page(page) |
176 | ||
176 | ||
177 | 177 | def is_showing_local_bookmark(self): |
178 | 178 | return self._is_showing_local_bookmark |
179 | 179 |
140 | 140 | self._search_find_last() |
141 | 141 | else: |
142 | 142 | self._search_find_prev() |
143 | ||
143 | ||
144 | 144 | def _find_next_cb(self, button): |
145 | 145 | if self._search_entry_changed: |
146 | 146 | self._search_find_first() |
181 | 181 | |
182 | 182 | self._evince_view = None |
183 | 183 | self._document = None |
184 | ||
184 | ||
185 | 185 | self._zoom_out = ToolButton('zoom-out') |
186 | 186 | self._zoom_out.set_tooltip(_('Zoom out')) |
187 | 187 | self._zoom_out.connect('clicked', self._zoom_out_cb) |
193 | 193 | self._zoom_in.connect('clicked', self._zoom_in_cb) |
194 | 194 | self.insert(self._zoom_in, -1) |
195 | 195 | self._zoom_in.show() |
196 | ||
196 | ||
197 | 197 | self._zoom_to_width = ToolButton('zoom-best-fit') |
198 | 198 | self._zoom_to_width.set_tooltip(_('Zoom to width')) |
199 | 199 | self._zoom_to_width.connect('clicked', self._zoom_to_width_cb) |
249 | 249 | self._view_notify_zoom_handler = self._evince_view.connect( |
250 | 250 | 'notify::zoom', self._view_notify_zoom_cb) |
251 | 251 | |
252 | self._update_zoom_buttons() | |
252 | self._update_zoom_buttons() | |
253 | 253 | |
254 | 254 | |
255 | 255 | def _zoom_spin_notify_value_cb(self, zoom_spin, pspec): |
286 | 286 | self._evince_view.props.sizing_mode = evince.SIZING_FREE |
287 | 287 | self._evince_view.zoom_out() |
288 | 288 | self._update_zoom_buttons() |
289 | ||
289 | ||
290 | 290 | def _zoom_out_cb(self, button): |
291 | 291 | self.zoom_out() |
292 | 292 |
45 | 45 | |
46 | 46 | def __init__(self, udi): |
47 | 47 | gobject.GObject.__init__(self) |
48 | ||
48 | ||
49 | 49 | bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) |
50 | 50 | proxy = bus.get_object('org.freedesktop.Hal', udi, |
51 | 51 | follow_name_owner_changes=True) |
91 | 91 | |
92 | 92 | def do_get_property(self, pspec): |
93 | 93 | if pspec.name == 'level': |
94 | return self._level | |
94 | return self._level | |
95 | 95 | if pspec.name == 'charging': |
96 | 96 | return self._charging |
97 | 97 | if pspec.name == 'discharging': |
173 | 173 | if self._battery is not None: |
174 | 174 | icon_name = get_icon_state(_ICON_NAME, self._battery.props.level, step=-5) |
175 | 175 | self._icon = Icon(icon_name=icon_name) |
176 | self.pack_start(self._icon, expand = False, fill = False) | |
176 | self.pack_start(self._icon, expand = False, fill = False) | |
177 | 177 | |
178 | 178 | def _battery_level_changed_cb(self, pspec, param): |
179 | 179 | icon_name = get_icon_state(_ICON_NAME, self._battery.props.level, step=-5) |
198 | 198 | |
199 | 199 | #TRANS: Translate this as Page i of m (eg: Page 4 of 334) |
200 | 200 | self._progressbar.set_text(_("Page %i of %i") % (current_page, n_pages)) |
201 | ||
202 | ||
203 | ||
201 | ||
202 | ||
203 |