Codebase list sugar-read-activity / c6dddeb
EPub: port to WebKit2 Due to the nature of the changem replacement of the rendering engine it, is impossible to split this into multiple commits sensibly. Here are the parts that were not straightforward to port (the most important ones first): * The WebView widget now does the scrolling. This allows us to do away with the "global scrollbar overlaying a scrolled view" hack. On the other hand, we don't own the scrolled view anymore and the WebView API can't notify us of scroll events nor be told to scroll to a particular position. We use Javascript to achieve that and we nest the WebView to steal the scroll events. * The text selection API is gone. We run Javascript scriptlets instead. * Test search/marking API is rather different, yet good enough. * Font size is now in units that are 3/4 of the previous ones. Perhaps has something to do with px/pt being lock to 72 or 96 dpi, whichever actually applies. This is important to account for so that the pagination stays the same as previously. * The JavaScript execution is asynchronous. This probably has to do with the layouting having been moved to a separate process. * Synchronous Javascript call is gone. Just firing up the script run and not hooking a callback works well in places where we don't need to return a value back to Python. The few places where this wouldn't be good enought use a helper that does the blocking, doing away with the document.title hack. Lastly, WebKit got visibly slower. The initial pagination seems to take like twice as long or so. I guess this has to do with the multi-process feature, or just some bloat creep. Oh well. Lubomir Rintel authored 5 years ago James Cameron committed 5 years ago
4 changed file(s) with 380 addition(s) and 362 deletion(s). Raw diff Collapse all Expand all
7575 self._view.set_editable(True)
7676
7777 if highlight:
78 self._view.execute_script(
79 'document.execCommand("backColor", false, "yellow");')
78 js = 'document.execCommand("backColor", false, "yellow");'
8079 else:
8180 # need remove the highlight nodes
82 js = """
83 var selObj = window.getSelection();
84 var range = selObj.getRangeAt(0);
85 var node = range.startContainer;
86 while (node.parentNode != null) {
87 if (node.localName == "span") {
88 if (node.hasAttributes()) {
89 var attrs = node.attributes;
90 for(var i = attrs.length - 1; i >= 0; i--) {
91 if (attrs[i].name == "style" &&
92 attrs[i].value == "background-color: yellow;") {
93 node.removeAttribute("style");
94 break;
81 js = '''
82 (function(){
83 var selObj = window.getSelection();
84 if (selObj.rangeCount < 1)
85 return;
86 var range = selObj.getRangeAt(0);
87 var node = range.startContainer;
88 while (node.parentNode != null) {
89 if (node.localName == "span") {
90 if (node.hasAttributes()) {
91 var attrs = node.attributes;
92 for (var i = attrs.length - 1; i >= 0; i--) {
93 if (attrs[i].name == "style" &&
94 attrs[i].value == "background-color: yellow;") {
95 node.removeAttribute("style");
96 break;
97 };
98 };
9599 };
96100 };
101 node = node.parentNode;
97102 };
98 };
99 node = node.parentNode;
100 };"""
101 self._view.execute_script(js)
103 }())
104 '''
105
106 self._view.run_javascript(js)
102107
103108 self._view.set_editable(False)
104109 # mark the file as modified
109114 GObject.idle_add(self._save_page)
110115
111116 def _save_page(self):
112 oldtitle = self._view.get_title()
113 self._view.execute_script(
114 "document.title=document.documentElement.innerHTML;")
115 html = self._view.get_title()
117 html = self._view._execute_script_sync("document.documentElement.innerHTML")
116118 file_path = self.get_current_file().replace('file:///', '/')
117119 logging.error(html)
118120 with open(file_path, 'w') as fd:
123125 fd.write(header)
124126 fd.write(html)
125127 fd.write('</html>')
126 self._view.execute_script('document.title=%s;' % oldtitle)
127128
128129 def save(self, file_path):
129130 if self._modified_files:
135136 def in_highlight(self):
136137 # Verify if the selection already exist or the cursor
137138 # is in a highlighted area
138 page_title = self._view.get_title()
139 js = """
140 var selObj = window.getSelection();
141 var range = selObj.getRangeAt(0);
142 var node = range.startContainer;
143 var onHighlight = false;
144 while (node.parentNode != null) {
145 if (node.localName == "span") {
146 if (node.hasAttributes()) {
147 var attrs = node.attributes;
148 for(var i = attrs.length - 1; i >= 0; i--) {
149 if (attrs[i].name == "style" &&
150 attrs[i].value == "background-color: yellow;") {
151 onHighlight = true;
139 return self._view._execute_script_sync("""
140 (function(){
141 var selObj = window.getSelection();
142 if (selObj.rangeCount < 1)
143 return false;
144 var range = selObj.getRangeAt(0);
145 var node = range.startContainer;
146 while (node.parentNode != null) {
147 if (node.localName == "span") {
148 if (node.hasAttributes()) {
149 var attrs = node.attributes;
150 for(var i = attrs.length - 1; i >= 0; i--) {
151 if (attrs[i].name == "style" &&
152 attrs[i].value == "background-color: yellow;") {
153 return true;
154 };
155 };
152156 };
153157 };
158 node = node.parentNode;
154159 };
155 };
156 node = node.parentNode;
157 };
158 document.title=onHighlight;"""
159 self._view.execute_script(js)
160 on_highlight = self._view.get_title() == 'true'
161 self._view.execute_script('document.title = "%s";' % page_title)
162 # the second parameter is only used in the text backend
163 return on_highlight, None
160 return false;
161 })()""") == "true", None;
164162
165163 def can_do_text_to_speech(self):
166164 return False
229227 return
230228
231229 def find_set_highlight_search(self, set_highlight_search):
232 self._view.set_highlight_text_matches(set_highlight_search)
230 pass
233231
234232 def set_current_page(self, n):
235233 # When the book is being loaded, calling this does not help
00 # Copyright 2009 One Laptop Per Child
11 # Author: Sayamindu Dasgupta <sayamindu@laptop.org>
2 # WebKit2 port Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
23 #
34 # This program is free software; you can redistribute it and/or modify
45 # it under the terms of the GNU General Public License as published by
1415 # along with this program; if not, write to the Free Software
1516 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1617
18 import gi
19 gi.require_version('WebKit2', '4.0')
20
1721 from gi.repository import Gtk
1822 from gi.repository import GObject
1923 from gi.repository import Gdk
24 from gi.repository import WebKit2
2025 import widgets
2126
2227 import logging
2732 from jobs import _JobPaginator as _Paginator
2833
2934 LOADING_HTML = '''
30 <div style="width:100%;height:100%;text-align:center;padding-top:50%;">
31 <h1>Loading...</h1>
32 </div>
35 <html style="height: 100%; margin: 0; padding: 0; width: 100%;">
36 <body style="display: table; height: 100%; margin: 0; padding: 0; width: 100%;">
37 <div style="display: table-cell; text-align: center; vertical-align: middle;">
38 <h1>Loading...</h1>
39 </div>
40 </body>
41 </html>
3342 '''
34
3543
3644 class _View(Gtk.HBox):
3745
5664 self._ready = False
5765 self._paginator = None
5866 self._loaded_page = -1
59 self._file_loaded = True
6067 # self._old_scrollval = -1
6168 self._loaded_filename = None
6269 self._pagecount = -1
63 self.__going_fwd = True
64 self.__going_back = False
70 self.__scroll_to_end = False
6571 self.__page_changed = False
6672 self._has_selection = False
73 self._scrollval = 0.0
6774 self.scale = 1.0
6875 self._epub = None
6976 self._findjob = None
7279 self._filelist = None
7380 self._internal_link = None
7481
75 self._sw = Gtk.ScrolledWindow()
7682 self._view = widgets._WebView()
77 self._view.load_string(LOADING_HTML, 'text/html', 'utf-8', '/')
83 self._view.load_html(LOADING_HTML, '/')
7884 settings = self._view.get_settings()
7985 settings.props.default_font_family = 'DejaVu LGC Serif'
8086 settings.props.enable_plugins = False
81 settings.props.default_encoding = 'utf-8'
82 self._view.connect('load-finished', self._view_load_finished_cb)
83 self._view.connect('scroll-event', self._view_scroll_event_cb)
84 self._view.connect('key-press-event', self._view_keypress_event_cb)
85 self._view.connect('selection-changed',
86 self._view_selection_changed_cb)
87 self._view.connect_after('populate-popup',
88 self._view_populate_popup_cb)
89 self._view.connect('touch-change-page', self.__touch_page_changed_cb)
90
91 self._sw.add(self._view)
92 self._v_vscrollbar = self._sw.get_vscrollbar()
93 self._v_scrollbar_value_changed_cb_id = self._v_vscrollbar.connect(
94 'value-changed', self._v_scrollbar_value_changed_cb)
87 settings.props.default_charset = 'utf-8'
88 self._view.connect('load-changed', self._view_load_changed_cb)
89 self._view.connect('scrolled', self._view_scrolled_cb)
90 self._view.connect('scrolled-top', self._view_scrolled_top_cb)
91 self._view.connect('scrolled-bottom', self._view_scrolled_bottom_cb)
92 self._view.connect('selection-changed', self._view_selection_changed_cb)
93
94 find = self._view.get_find_controller()
95 find.connect('failed-to-find-text', self._find_failed_cb)
96
97 self._eventbox = Gtk.EventBox()
98 self._eventbox.connect('scroll-event', self._eventbox_scroll_event_cb)
99 self._eventbox.add_events(Gdk.EventMask.SCROLL_MASK)
100 self._eventbox.add(self._view)
101
95102 self._scrollbar = Gtk.VScrollbar()
96103 self._scrollbar_change_value_cb_id = self._scrollbar.connect(
97104 'change-value', self._scrollbar_change_value_cb)
98105
99 overlay = Gtk.Overlay()
100106 hbox = Gtk.HBox()
101 overlay.add(hbox)
102 hbox.add(self._sw)
103
104 self._scrollbar.props.halign = Gtk.Align.END
105 self._scrollbar.props.valign = Gtk.Align.FILL
106 overlay.add_overlay(self._scrollbar)
107
108 self.pack_start(overlay, True, True, 0)
109
107 hbox.pack_start(self._eventbox, True, True, 0)
108 hbox.pack_end(self._scrollbar, False, True, 0)
109
110 self.pack_start(hbox, True, True, 0)
110111 self._view.set_can_default(True)
111112 self._view.set_can_focus(True)
112113
141142 '''
142143 Returns True if any part of the content is selected
143144 '''
144 return self._view.can_copy_clipboard()
145 return self._has_selection
145146
146147 def get_zoom(self):
147148 '''
197198 """
198199 Used to save the scrolled position and restore when needed
199200 """
200 return self._v_vscrollbar.get_adjustment().get_value()
201 return self._scrollval
201202
202203 def set_vertical_pos(self, position):
203204 """
204205 Used to save the scrolled position and restore when needed
205206 """
206 self._v_vscrollbar.get_adjustment().set_value(position)
207 self._view.scroll_to(position)
207208
208209 def can_zoom_in(self):
209210 '''
281282 Valid scrolltypes are:
282283 Gtk.ScrollType.PAGE_BACKWARD, Gtk.ScrollType.PAGE_FORWARD,
283284 Gtk.ScrollType.STEP_BACKWARD, Gtk.ScrollType.STEP_FORWARD
284 Gtk.ScrollType.STEP_START and Gtk.ScrollType.STEP_STOP
285 Gtk.ScrollType.STEP_START and Gtk.ScrollType.STEP_END
285286 '''
286287 if scrolltype == Gtk.ScrollType.PAGE_BACKWARD:
287 self.__going_back = True
288 self.__going_fwd = False
289 if not self._do_page_transition():
290 self._view.move_cursor(Gtk.MovementStep.PAGES, -1)
288 pages = self._paginator.get_pagecount_for_file(self._loaded_filename)
289 self._view.scroll_by(self._page_height / pages * -1)
291290 elif scrolltype == Gtk.ScrollType.PAGE_FORWARD:
292 self.__going_back = False
293 self.__going_fwd = True
294 if not self._do_page_transition():
295 self._view.move_cursor(Gtk.MovementStep.PAGES, 1)
291 pages = self._paginator.get_pagecount_for_file(self._loaded_filename)
292 self._view.scroll_by(self._page_height / pages * 1)
296293 elif scrolltype == Gtk.ScrollType.STEP_BACKWARD:
297 self.__going_fwd = False
298 self.__going_back = True
299 if not self._do_page_transition():
300 self._view.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1)
294 self._view.scroll_by(self._view.get_settings().get_default_font_size() * -3)
301295 elif scrolltype == Gtk.ScrollType.STEP_FORWARD:
302 self.__going_fwd = True
303 self.__going_back = False
304 if not self._do_page_transition():
305 self._view.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1)
296 self._view.scroll_by(self._view.get_settings().get_default_font_size() * 3)
306297 elif scrolltype == Gtk.ScrollType.START:
307 self.__going_back = True
308 self.__going_fwd = False
309 if not self._do_page_transition():
310 self.set_current_page(1)
298 self.set_current_page(0)
311299 elif scrolltype == Gtk.ScrollType.END:
312 self.__going_back = False
313 self.__going_fwd = True
314 if not self._do_page_transition():
315 self.set_current_page(self._pagecount - 1)
316 else:
317 print ('Got unsupported scrolltype %s' % str(scrolltype))
300 self.__scroll_to_end = True
301 self.set_current_page(self._pagecount - 1)
302 else:
303 print('Got unsupported scrolltype %s' % str(scrolltype))
318304
319305 def __touch_page_changed_cb(self, widget, forward):
320306 if forward:
326312 '''
327313 Copies the current selection to clipboard.
328314 '''
329 self._view.copy_clipboard()
315 self._view.run_javascript('document.execCommand("copy")')
330316
331317 def find_next(self):
332318 '''
333319 Highlights the next matching item for current search
334320 '''
335321 self._view.grab_focus()
336
337 if self._view.search_text(self._findjob.get_search_text(),
338 self._findjob.get_case_sensitive(),
339 True, False):
340 return
341 else:
342 path = os.path.join(self._epub.get_basedir(),
343 self._findjob.get_next_file())
322 self.__search_fwd = True
323 self._view.get_find_controller().search_next()
324
325 def find_previous(self):
326 '''
327 Highlights the previous matching item for current search
328 '''
329 self._view.grab_focus()
330 self.__search_fwd = False
331 self._view.get_find_controller().search_previous()
332
333 def _find_failed_cb(self, find_controller):
334 try:
335 if self.__search_fwd:
336 path = os.path.join(self._epub.get_basedir(),
337 self._findjob.get_next_file())
338 else:
339 path = os.path.join(self._epub.get_basedir(),
340 self._findjob.get_prev_file())
344341 self.__in_search = True
345 self.__search_fwd = True
346342 self._load_file(path)
347
348 def find_previous(self):
349 '''
350 Highlights the previous matching item for current search
351 '''
352 self._view.grab_focus()
353
354 if self._view.search_text(self._findjob.get_search_text(),
355 self._findjob.get_case_sensitive(),
356 False, False):
357 return
358 else:
359 path = os.path.join(self._epub.get_basedir(),
360 self._findjob.get_prev_file())
361 self.__in_search = True
362 self.__search_fwd = False
363 self._load_file(path)
343 except IndexError:
344 # No match anywhere, no other file to pick
345 pass
364346
365347 def _find_changed(self, job):
366348 self._view.grab_focus()
367349 self._findjob = job
368 self._mark_found_text()
369 self.find_next()
370
371 def _mark_found_text(self):
372 self._view.unmark_text_matches()
373 self._view.mark_text_matches(
374 self._findjob.get_search_text(),
375 case_sensitive=self._findjob.get_case_sensitive(), limit=0)
376 self._view.set_highlight_text_matches(True)
350 find = self._view.get_find_controller()
351 find.search (self._findjob.get_search_text(),
352 self._findjob.get_flags(),
353 GObject.G_MAXUINT)
377354
378355 def __set_zoom(self, value):
379356 self._view.set_zoom_level(value)
380357 self.scale = value
381358
382 def _view_populate_popup_cb(self, view, menu):
383 menu.destroy() # HACK
384 return
385
386 def _view_selection_changed_cb(self, view):
359 def _view_scrolled_cb(self, view, scrollval):
360 if self._loaded_page < 1:
361 return
362
363 self._scrollval = scrollval
364 scroll_upper = self._page_height
365 scroll_page_size = self._view.get_allocated_height()
366
367 if scrollval > 0:
368 scrollfactor = scrollval / (scroll_upper - scroll_page_size)
369 else:
370 scrollfactor = 0
371
372 if not self._loaded_page == self._pagecount and \
373 not self._paginator.get_file_for_pageno(self._loaded_page) != \
374 self._paginator.get_file_for_pageno(self._loaded_page + 1):
375
376 scrollfactor_next = \
377 self._paginator.get_scrollfactor_pos_for_pageno(
378 self._loaded_page + 1)
379 if scrollfactor >= scrollfactor_next:
380 self._on_page_changed(self._loaded_page, self._loaded_page + 1)
381 return
382
383 if self._loaded_page > 1 and \
384 not self._paginator.get_file_for_pageno(self._loaded_page) != \
385 self._paginator.get_file_for_pageno(self._loaded_page - 1):
386
387 scrollfactor_cur = \
388 self._paginator.get_scrollfactor_pos_for_pageno(
389 self._loaded_page)
390 if scrollfactor <= scrollfactor_cur:
391 self._on_page_changed(self._loaded_page, self._loaded_page - 1)
392 return
393
394 def _view_scrolled_top_cb(self, view):
395 if self._loaded_page > 1:
396 self.__scroll_to_end = True
397 self._load_prev_page()
398
399 def _view_scrolled_bottom_cb(self, view):
400 if self._loaded_page < self._pagecount:
401 self._load_next_page()
402
403 def _view_selection_changed_cb(self, view, has_selection):
404 self._has_selection = has_selection
387405 self.emit('selection-changed')
388406
389 def _view_keypress_event_cb(self, view, event):
390 name = Gdk.keyval_name(event.keyval)
391 if name == 'Page_Down' or name == 'Down':
392 self.__going_back = False
393 self.__going_fwd = True
394 elif name == 'Page_Up' or name == 'Up':
395 self.__going_back = True
396 self.__going_fwd = False
397
398 self._do_page_transition()
399
400 def _view_scroll_event_cb(self, view, event):
407 def _eventbox_scroll_event_cb(self, view, event):
401408 if event.direction == Gdk.ScrollDirection.DOWN:
402 self.__going_back = False
403 self.__going_fwd = True
409 self.scroll(Gtk.ScrollType.STEP_FORWARD, False)
404410 elif event.direction == Gdk.ScrollDirection.UP:
405 self.__going_back = True
406 self.__going_fwd = False
407
408 self._do_page_transition()
409
410 def _do_page_transition(self):
411 if self.__going_fwd:
412 if self._v_vscrollbar.get_value() >= \
413 self._v_vscrollbar.props.adjustment.props.upper - \
414 self._v_vscrollbar.props.adjustment.props.page_size:
415 self._load_page(self._loaded_page + 1)
416 return True
417 elif self.__going_back:
418 if self._v_vscrollbar.get_value() == \
419 self._v_vscrollbar.props.adjustment.props.lower:
420 self._load_page(self._loaded_page - 1)
421 return True
422
423 return False
424
425 def _view_load_finished_cb(self, v, frame):
426
427 self._file_loaded = True
411 self.scroll(Gtk.ScrollType.STEP_BACKWARD, False)
412
413 def _view_load_changed_cb(self, v, load_event):
414 if load_event != WebKit2.LoadEvent.FINISHED:
415 return True
416
428417 filename = self._view.props.uri.replace('file://', '')
429418 if os.path.exists(filename.replace('xhtml', 'xml')):
430419 # Hack for making javascript work
443432 remfactor * self._view.get_page_height() / (pages - remfactor)))
444433 if extra > 0:
445434 self._view.add_bottom_padding(extra)
435 self._page_height = self._view.get_page_height()
446436
447437 if self.__in_search:
448 self._mark_found_text()
449 self._view.search_text(self._findjob.get_search_text(),
450 self._findjob.get_case_sensitive(),
451 self.__search_fwd, False)
452438 self.__in_search = False
453 else:
454 if self.__going_back:
455 # We need to scroll to the last page
456 self._scroll_page_end()
457 else:
458 self._scroll_page()
439 find = self._view.get_find_controller()
440 find.search (self._findjob.get_search_text(),
441 self._findjob.get_flags(self.__search_fwd),
442 GObject.G_MAXUINT)
443 else:
444 self._scroll_page()
459445
460446 # process_file = True
461447 if self._internal_link is not None:
473459 # if the link is at the bottom of the page, we open the next file
474460 one_page_height = self._paginator.get_single_page_height()
475461 self._internal_link = None
476 if vertical_pos > self._view.get_page_height() - one_page_height:
462 if vertical_pos > self._page_height - one_page_height:
477463 logging.error('bottom page link, go to next file')
478464 next_file = self._paginator.get_next_filename(filename)
479465 if next_file is not None:
480466 logging.error('load next file %s', next_file)
481467 self.__in_search = False
482 self.__going_back = False
468 self.__scroll_to_end = False
483469 # process_file = False
484470 GObject.idle_add(self._load_file, next_file)
485471
516502 self.word_tuples.append(word_tuple)
517503 i = i + 1
518504
519 def _scroll_page_end(self):
520 v_upper = self._v_vscrollbar.props.adjustment.props.upper
521 # v_page_size = self._v_vscrollbar.props.adjustment.props.page_size
522 self._v_vscrollbar.set_value(v_upper)
523
524505 def _scroll_page(self):
525 pageno = self._loaded_page
526
527 v_upper = self._v_vscrollbar.props.adjustment.props.upper
528 v_page_size = self._v_vscrollbar.props.adjustment.props.page_size
529
530 scrollfactor = self._paginator.get_scrollfactor_pos_for_pageno(pageno)
531 self._v_vscrollbar.set_value((v_upper - v_page_size) * scrollfactor)
506 v_upper = self._page_height
507 if self.__scroll_to_end:
508 # We need to scroll to the last page
509 scrollval = v_upper
510 self.__scroll_to_end = False
511 else:
512 pageno = self._loaded_page
513 scrollfactor = self._paginator.get_scrollfactor_pos_for_pageno(pageno)
514 scrollval = math.ceil(v_upper * scrollfactor)
515 self._view.scroll_to(scrollval)
532516
533517 def _paginate(self):
534518 filelist = []
550534
551535 def _load_prev_page(self):
552536 self._load_page(self._loaded_page - 1)
553
554 def _v_scrollbar_value_changed_cb(self, scrollbar):
555 if self._loaded_page < 1:
556 return
557 scrollval = scrollbar.get_value()
558 scroll_upper = self._v_vscrollbar.props.adjustment.props.upper
559 scroll_page_size = self._v_vscrollbar.props.adjustment.props.page_size
560
561 if self.__going_fwd and not self._loaded_page == self._pagecount:
562 if self._paginator.get_file_for_pageno(self._loaded_page) != \
563 self._paginator.get_file_for_pageno(self._loaded_page + 1):
564 # We don't need this if the next page is in another file
565 return
566
567 scrollfactor_next = \
568 self._paginator.get_scrollfactor_pos_for_pageno(
569 self._loaded_page + 1)
570 if scrollval > 0:
571 scrollfactor = scrollval / (scroll_upper - scroll_page_size)
572 else:
573 scrollfactor = 0
574 if scrollfactor >= scrollfactor_next:
575 self._on_page_changed(self._loaded_page, self._loaded_page + 1)
576 elif self.__going_back and self._loaded_page > 1:
577 if self._paginator.get_file_for_pageno(self._loaded_page) != \
578 self._paginator.get_file_for_pageno(self._loaded_page - 1):
579 return
580
581 scrollfactor_cur = \
582 self._paginator.get_scrollfactor_pos_for_pageno(
583 self._loaded_page)
584 if scrollval > 0:
585 scrollfactor = scrollval / (scroll_upper - scroll_page_size)
586 else:
587 scrollfactor = 0
588
589 if scrollfactor <= scrollfactor_cur:
590 self._on_page_changed(self._loaded_page, self._loaded_page - 1)
591537
592538 def _on_page_changed(self, oldpage, pageno):
593539 if oldpage == pageno:
607553 if self._loaded_page == pageno:
608554 return
609555
556 oldpage = self._loaded_page
557
610558 filename = self._paginator.get_file_for_pageno(pageno)
611559 filename = filename.replace('file://', '')
612560
613561 if filename != self._loaded_filename:
614562 self._loaded_filename = filename
615 if not self._file_loaded:
616 # wait until the file is loaded
617 return
618 self._file_loaded = False
619563
620564 """
621565 TODO: disabled because javascript can't be executed
629573 now text highlight is implemented and the epub file is saved
630574 """
631575
576 self._view.stop_loading()
632577 if filename.endswith('xml'):
633578 dest = filename.replace('xml', 'xhtml')
634579 if not os.path.exists(dest):
639584 else:
640585 self._loaded_page = pageno
641586 self._scroll_page()
642 self._on_page_changed(self._loaded_page, pageno)
587 self._on_page_changed(oldpage, pageno)
643588
644589 def _insert_js_reference(self, file_name, path):
645590 js_reference = '<script type="text/javascript" ' + \
668613 break
669614
670615 def _scrollbar_change_value_cb(self, range, scrolltype, value):
671 if scrolltype == Gtk.ScrollType.STEP_FORWARD:
672 self.__going_fwd = True
673 self.__going_back = False
674 if not self._do_page_transition():
675 self._view.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1)
676 elif scrolltype == Gtk.ScrollType.STEP_BACKWARD:
677 self.__going_fwd = False
678 self.__going_back = True
679 if not self._do_page_transition():
680 self._view.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1)
616 if scrolltype == Gtk.ScrollType.STEP_FORWARD or \
617 scrolltype == Gtk.ScrollType.STEP_BACKWARD:
618 self.scroll(scrolltype, False)
681619 elif scrolltype == Gtk.ScrollType.JUMP or \
682 scrolltype == Gtk.ScrollType.PAGE_FORWARD or \
620 scrolltype == Gtk.ScrollType.PAGE_FORWARD or \
683621 scrolltype == Gtk.ScrollType.PAGE_BACKWARD:
684622 if value > self._scrollbar.props.adjustment.props.upper:
685623 self._load_page(self._pagecount)
686624 else:
687 self._load_page(round(value))
625 self._load_page(int(value))
688626 else:
689627 print 'Warning: unknown scrolltype %s with value %f' \
690628 % (str(scrolltype), value)
702640 self._ready = True
703641
704642 self._pagecount = self._paginator.get_total_pagecount()
705 self._scrollbar.set_range(1.0, self._pagecount - 1.0)
643 self._scrollbar.set_range(1.0, self._pagecount)
706644 self._scrollbar.set_increments(1.0, 1.0)
707645 self._view.grab_focus()
708646 self._view.grab_default()
00 # Copyright 2009 One Laptop Per Child
11 # Author: Sayamindu Dasgupta <sayamindu@laptop.org>
2 # WebKit2 port Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
23 #
34 # This program is free software; you can redistribute it and/or modify
45 # it under the terms of the GNU General Public License as published by
1415 # along with this program; if not, write to the Free Software
1516 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1617
18 import gi
19 gi.require_version('WebKit2', '4.0')
1720
1821 from gi.repository import GObject
1922 from gi.repository import Gtk
23 from gi.repository import Gdk
24 from gi.repository import WebKit2
2025 import widgets
2126 import math
2227 import os.path
120125 """
121126
122127 self._temp_win = Gtk.Window()
123 self._temp_view = widgets._WebView(only_to_measure=True)
128 self._temp_view = widgets._WebView()
124129
125130 settings = self._temp_view.get_settings()
126131 settings.props.default_font_family = 'DejaVu LGC Serif'
127132 settings.props.sans_serif_font_family = 'DejaVu LGC Sans'
128133 settings.props.serif_font_family = 'DejaVu LGC Serif'
129134 settings.props.monospace_font_family = 'DejaVu LGC Sans Mono'
130 settings.props.enforce_96_dpi = True
131135 # FIXME: This does not seem to work
132136 # settings.props.auto_shrink_images = False
133137 settings.props.enable_plugins = False
134 settings.props.default_font_size = 12
135 settings.props.default_monospace_font_size = 10
136 settings.props.default_encoding = 'utf-8'
137
138 sw = Gtk.ScrolledWindow()
139 sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
140 self._dpi = 96
138 settings.props.default_font_size = 16
139 settings.props.default_monospace_font_size = 13
140 settings.props.default_charset = 'utf-8'
141
142 self._dpi = Gdk.Screen.get_default().get_resolution()
141143 self._single_page_height = _mm_to_pixel(PAGE_HEIGHT, self._dpi)
142 sw.set_size_request(_mm_to_pixel(PAGE_WIDTH, self._dpi),
143 self._single_page_height)
144 sw.add(self._temp_view)
145 self._temp_win.add(sw)
146 self._temp_view.connect('load-finished', self._page_load_finished_cb)
144 self._temp_view.set_size_request(_mm_to_pixel(PAGE_WIDTH, self._dpi), self._single_page_height)
145
146 self._temp_win.add(self._temp_view)
147 self._temp_view.connect('load-changed', self._page_load_changed_cb)
147148
148149 self._temp_win.show_all()
149150 self._temp_win.unmap()
150151
151 self._temp_view.open(self._filelist[self._count])
152 self._temp_view.load_uri('file://' + self._filelist[self._count])
152153
153154 def get_single_page_height(self):
154155 """
164165 return self._filelist[n + 1]
165166 return None
166167
167 def _page_load_finished_cb(self, v, frame):
168 f = v.get_main_frame()
168 def _page_load_changed_cb(self, v, load_event):
169 if load_event != WebKit2.LoadEvent.FINISHED:
170 return True
171
169172 pageheight = v.get_page_height()
170173
171174 if pageheight <= self._single_page_height:
178181 else:
179182 pagelen = 1 / pages
180183 self._pagemap[float(self._pagecount + i)] = \
181 (f.props.uri, (i - 1) / math.ceil(pages), pagelen)
184 (v.get_uri(), (i - 1) / math.ceil(pages), pagelen)
182185
183186 self._pagecount += int(math.ceil(pages))
184 self._filedict[f.props.uri.replace('file://', '')] = \
187 self._filedict[v.get_uri().replace('file://', '')] = \
185188 (math.ceil(pages), math.ceil(pages) - pages)
186189 self._bookheight += pageheight
187190
190193 # self._screen.set_font_options(self._old_fontoptions)
191194 self.emit('paginated')
192195 GObject.idle_add(self._cleanup)
196
193197 else:
194198 self._count += 1
195 self._temp_view.open(self._filelist[self._count])
199 self._temp_view.load_uri('file://' + self._filelist[self._count])
196200
197201 def _cleanup(self):
198202 self._temp_win.destroy()
323327 '''
324328 return self._text
325329
326 def get_case_sensitive(self):
327 '''
328 Returns True if the search is case-sensitive
329 '''
330 return self._case_sensitive
330 def get_flags(self, forward=True):
331 '''
332 Returns the search flags
333 '''
334 flags = WebKit2.FindOptions.NONE
335 if self._case_sensitive:
336 flags = flags | WebKit2.FindOptions.CASE_INSENSITIVE
337 if not forward:
338 flags = flags | WebKit2.FindOptions.BACKWARDS
339 return flags
00 import logging
11
22 import gi
3 gi.require_version('WebKit', '3.0')
3 gi.require_version('WebKit2', '4.0')
4 gi.require_version('Gtk', '3.0')
45
5 from gi.repository import WebKit
6 from gi.repository import GLib
7 from gi.repository import WebKit2
8 from gi.repository import Gtk
69 from gi.repository import Gdk
710 from gi.repository import GObject
811
9
10 class _WebView(WebKit.WebView):
12 class _WebView(WebKit2.WebView):
1113
1214 __gsignals__ = {
1315 'touch-change-page': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
14 ([bool])), }
16 ([bool])),
17 'scrolled': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
18 ([float])),
19 'scrolled-top': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
20 ([])),
21 'scrolled-bottom': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
22 ([])),
23 'selection-changed': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
24 ([bool])),
25 }
1526
16 def __init__(self, only_to_measure=False):
17 WebKit.WebView.__init__(self)
18 self._only_to_measure = only_to_measure
27 def __init__(self, **kwargs):
28 cm = WebKit2.UserContentManager()
29
30 cm.register_script_message_handler('scrolled');
31 cm.connect('script-message-received::scrolled',
32 lambda cm, result: self.emit('scrolled',
33 result.get_js_value().to_double()))
34
35 cm.register_script_message_handler('scrolled_top');
36 cm.connect('script-message-received::scrolled_top',
37 lambda cm, result: self.emit('scrolled-top'))
38
39 cm.register_script_message_handler('scrolled_bottom');
40 cm.connect('script-message-received::scrolled_bottom',
41 lambda cm, result: self.emit('scrolled-bottom'))
42
43 cm.register_script_message_handler('selection_changed');
44 cm.connect('script-message-received::selection_changed',
45 lambda cm, result: self.emit('selection-changed',
46 result.get_js_value().to_boolean()))
47
48 cm.add_script(WebKit2.UserScript('''
49 window.addEventListener("scroll", function(){
50 var handler = window.webkit.messageHandlers.scrolled;
51 handler.postMessage(window.scrollY);
52 });
53 document.addEventListener("selectionchange", function() {
54 var handler = window.webkit.messageHandlers.selection_changed;
55 handler.postMessage(window.getSelection() != '');
56 });
57 ''',
58 WebKit2.UserContentInjectedFrames.ALL_FRAMES,
59 WebKit2.UserScriptInjectionTime.START, None, None))
60
61 cm.add_style_sheet(WebKit2.UserStyleSheet('''
62 html { margin: 50px; }
63 body { overflow: hidden; }
64 ''',
65 WebKit2.UserContentInjectedFrames.ALL_FRAMES,
66 WebKit2.UserStyleLevel.USER, None, None))
67
68 WebKit2.WebView.__init__(self, user_content_manager=cm, **kwargs)
69 self.get_settings().set_enable_write_console_messages_to_stdout(True)
70
71 def do_context_menu (self, context_menu, event, hit_test_result):
72 # nope nope nope nopenopenopenenope
73 return True
1974
2075 def setup_touch(self):
2176 self.get_window().set_events(
3186 elif x < view_width * 1 / 4:
3287 self.emit('touch-change-page', False)
3388
89 def _execute_script_sync(self, js):
90 '''
91 This sad function aims to provide synchronous script execution like
92 WebKit-1.0's WebView.execute_script() to ease porting.
93 '''
94 res = ["0"]
95
96 def callback(self, task, user_data):
97 Gtk.main_quit()
98 result = self.run_javascript_finish(task)
99 if result is not None:
100 res[0] = result.get_js_value().to_string()
101
102 self.run_javascript(js, None, callback, None)
103 Gtk.main()
104 return res[0]
105
34106 def get_page_height(self):
35107 '''
36108 Gets height (in pixels) of loaded (X)HTML page.
37109 This is done via javascript at the moment
38110 '''
39 hide_scrollbar_js = ''
40 if self._only_to_measure:
41 hide_scrollbar_js = \
42 'document.documentElement.style.overflow = "hidden";'
43
44 oldtitle = self.get_main_frame().get_title()
45
46 js = """
47 document.documentElement.style.margin = "50px";
48 if (document.body == null) {
49 document.title = 0;
50 } else {
51 %s
52 document.title=Math.max(document.body.scrollHeight,
53 document.body.offsetHeight,
54 document.documentElement.clientHeight,
55 document.documentElement.scrollHeight,
56 document.documentElement.offsetHeight);
57 };
58 """ % hide_scrollbar_js
59 self.execute_script(js)
60 ret = self.get_main_frame().get_title()
61 self.execute_script('document.title=%s;' % oldtitle)
62 try:
63 return int(ret)
64 except ValueError:
65 return 0
111 return int(self._execute_script_sync('''
112 (function(){
113 if (document.body == null) {
114 return 0;
115 } else {
116 return Math.max(document.body.scrollHeight,
117 document.body.offsetHeight,
118 document.documentElement.clientHeight,
119 document.documentElement.scrollHeight,
120 document.documentElement.offsetHeight);
121 };
122 })()
123 '''))
66124
67125 def add_bottom_padding(self, incr):
68126 '''
69 Adds incr pixels of padding to the end of the loaded (X)HTML page.
70 This is done via javascript at the moment
127 Adds incr pixels of margin to the end of the loaded (X)HTML page.
71128 '''
72 js = """
73 var newdiv = document.createElement("div");
74 newdiv.style.height = "%dpx";
75 document.body.appendChild(newdiv);
76 """ % incr
77 self.execute_script(js)
129 self.run_javascript('document.body.style.marginBottom = "%dpx";' % (incr + 50))
78130
79131 def highlight_next_word(self):
80132 '''
81133 Highlight next word (for text to speech)
82134 '''
83 self.execute_script('highLightNextWord();')
135 self.run_javascript('highLightNextWord();')
84136
85137 def go_to_link(self, id_link):
86 self.execute_script('window.location.href = "%s";' % id_link)
138 self.run_javascript('window.location.href = "%s";' % id_link)
87139
88140 def get_vertical_position_element(self, id_link):
89141 '''
91143 '''
92144 # remove the first '#' char
93145 id_link = id_link[1:]
94 oldtitle = self.get_main_frame().get_title()
95 js = """
96 obj = document.getElementById('%s');
97 var top = 0;
98 if(obj.offsetParent) {
99 while(1) {
100 top += obj.offsetTop;
101 if(!obj.offsetParent) {
102 break;
103 };
104 obj = obj.offsetParent;
105 };
106 } else if(obj.y) {
107 top += obj.y;
108 };
109 document.title=top;""" % id_link
110 self.execute_script(js)
111 ret = self.get_main_frame().get_title()
112 self.execute_script('document.title=%s;' % oldtitle)
113 try:
114 return int(ret)
115 except ValueError:
116 return 0
146 return int(self._execute_script_sync('''
147 (function(id_link){
148 var obj = document.getElementById(id_link);
149 var top = 0;
150 if (obj.offsetParent) {
151 while(1) {
152 top += obj.offsetTop;
153 if (!obj.offsetParent) {
154 break;
155 };
156 obj = obj.offsetParent;
157 };
158 } else if (obj.y) {
159 top += obj.y;
160 }
161 return top;
162 })("%s")
163 ''' % id_link))
164
165 def scroll_to(self, to):
166 '''
167 Set the vertical position in a document to a value in pixels.
168 '''
169 self.run_javascript('window.scrollTo(-1, %d);' % to)
170
171 def scroll_by(self, by):
172 '''
173 Modify the vertical position in a document by a value in pixels.
174 '''
175 self.run_javascript('''
176 (function(by){
177 var before = window.scrollY;
178 window.scrollBy(0, by);
179 if (window.scrollY == before) {
180 if (by < 0) {
181 var handler = window.webkit.messageHandlers.scrolled_top;
182 handler.postMessage(window.scrollY);
183 } else if (by > 0) {
184 var handler = window.webkit.messageHandlers.scrolled_bottom;
185 handler.postMessage(window.scrollY);
186 }
187 }
188 }(%d))
189 ''' % by)