Show the bookmarks in a tray like in Browse activity
The preview is saved in another table in the sqlite db.
Signed-off-by: Gonzalo Odiard <gonzalo@laptop.org>
Gonzalo Odiard
11 years ago
0 | # Copyright (C) 2007, One Laptop Per Child | |
1 | # | |
2 | # This library is free software; you can redistribute it and/or | |
3 | # modify it under the terms of the GNU Lesser General Public | |
4 | # License as published by the Free Software Foundation; either | |
5 | # version 2 of the License, or (at your option) any later version. | |
6 | # | |
7 | # This library is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
10 | # Lesser General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU Lesser General Public | |
13 | # License along with this library; if not, write to the | |
14 | # Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
15 | # Boston, MA 02111-1307, USA. | |
16 | ||
17 | from gi.repository import Gtk | |
18 | from gi.repository import Gdk | |
19 | from gi.repository import GdkPixbuf | |
20 | from gi.repository import GObject | |
21 | from gi.repository import Rsvg | |
22 | ||
23 | import os | |
24 | import StringIO | |
25 | import cairo | |
26 | from gettext import gettext as _ | |
27 | import re | |
28 | import gc | |
29 | import logging | |
30 | ||
31 | from sugar3.graphics.palette import Palette | |
32 | from sugar3.graphics.tray import TrayButton | |
33 | from sugar3.graphics import style | |
34 | ||
35 | ||
36 | class LinkButton(TrayButton, GObject.GObject): | |
37 | __gtype_name__ = 'LinkButton' | |
38 | __gsignals__ = { | |
39 | 'remove_link': (GObject.SignalFlags.RUN_FIRST, | |
40 | None, ([int])), | |
41 | 'go_to_bookmark': (GObject.SignalFlags.RUN_FIRST, | |
42 | None, ([int])), | |
43 | } | |
44 | ||
45 | def __init__(self, buf, color, title, owner, page): | |
46 | TrayButton.__init__(self) | |
47 | ||
48 | # Color read from the Journal may be Unicode, but Rsvg needs | |
49 | # it as single byte string: | |
50 | if isinstance(color, unicode): | |
51 | color = str(color) | |
52 | self.set_image(buf, color.split(',')[1], color.split(',')[0]) | |
53 | ||
54 | self.page = int(page) | |
55 | info = title + '\n' + owner | |
56 | self.setup_rollover_options(info) | |
57 | ||
58 | def set_image(self, buf, fill='#0000ff', stroke='#4d4c4f'): | |
59 | img = Gtk.Image() | |
60 | str_buf = StringIO.StringIO(buf) | |
61 | thumb_surface = cairo.ImageSurface.create_from_png(str_buf) | |
62 | ||
63 | bg_width, bg_height = style.zoom(120), style.zoom(110) | |
64 | bg_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, bg_width, | |
65 | bg_height) | |
66 | context = cairo.Context(bg_surface) | |
67 | # draw a rectangle in the background with the selected colors | |
68 | context.set_line_width(style.zoom(10)) | |
69 | context.set_source_rgba(*style.Color(fill).get_rgba()) | |
70 | context.rectangle(0, 0, bg_width, bg_height) | |
71 | context.fill_preserve() | |
72 | context.set_source_rgba(*style.Color(stroke).get_rgba()) | |
73 | context.stroke() | |
74 | # add the screenshot | |
75 | dest_x = style.zoom(10) | |
76 | dest_y = style.zoom(20) | |
77 | context.set_source_surface(thumb_surface, dest_x, dest_y) | |
78 | thumb_width, thumb_height = style.zoom(100), style.zoom(80) | |
79 | context.rectangle(dest_x, dest_y, thumb_width, thumb_height) | |
80 | context.fill() | |
81 | ||
82 | pixbuf_bg = Gdk.pixbuf_get_from_surface(bg_surface, 0, 0, | |
83 | bg_width, bg_height) | |
84 | ||
85 | img.set_from_pixbuf(pixbuf_bg) | |
86 | self.set_icon_widget(img) | |
87 | img.show() | |
88 | ||
89 | def setup_rollover_options(self, info): | |
90 | palette = Palette(info, text_maxlen=50) | |
91 | self.set_palette(palette) | |
92 | ||
93 | menu_item = Gtk.MenuItem(_('Go to Bookmark')) | |
94 | menu_item.connect('activate', self.go_to_bookmark_cb) | |
95 | palette.menu.append(menu_item) | |
96 | menu_item.show() | |
97 | ||
98 | menu_item = Gtk.MenuItem(_('Remove')) | |
99 | menu_item.connect('activate', self.item_remove_cb) | |
100 | palette.menu.append(menu_item) | |
101 | menu_item.show() | |
102 | ||
103 | def item_remove_cb(self, widget): | |
104 | self.emit('remove_link', self.page) | |
105 | ||
106 | def go_to_bookmark_cb(self, widget): | |
107 | self.emit('go_to_bookmark', self.page) |
22 | 22 | from gettext import gettext as _ |
23 | 23 | import re |
24 | 24 | import md5 |
25 | import StringIO | |
26 | import cairo | |
25 | 27 | |
26 | 28 | import dbus |
27 | 29 | from gi.repository import GObject |
41 | 43 | from sugar3.graphics.alert import ConfirmationAlert |
42 | 44 | from sugar3.activity.widgets import ActivityToolbarButton |
43 | 45 | from sugar3.activity.widgets import StopButton |
46 | from sugar3.graphics.tray import HTray | |
44 | 47 | from sugar3 import network |
45 | 48 | from sugar3 import mime |
49 | from sugar3 import profile | |
46 | 50 | |
47 | 51 | from sugar3.datastore import datastore |
48 | 52 | from sugar3.graphics.objectchooser import ObjectChooser |
53 | 57 | from bookmarkview import BookmarkView |
54 | 58 | from readdb import BookmarkManager |
55 | 59 | from sugarmenuitem import SugarMenuItem |
60 | from linkbutton import LinkButton | |
56 | 61 | |
57 | 62 | _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager' |
58 | 63 | _HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager' |
150 | 155 | self._bookmark_view.connect('bookmark-changed', |
151 | 156 | self._update_bookmark_cb) |
152 | 157 | |
158 | tray = HTray() | |
159 | self.set_tray(tray, Gtk.PositionType.BOTTOM) | |
160 | ||
153 | 161 | toolbar_box = ToolbarBox() |
154 | 162 | |
155 | 163 | self.activity_button = ActivityToolbarButton(self) |
176 | 184 | self.__view_toolbar_go_fullscreen_cb) |
177 | 185 | self._view_toolbar.connect('toggle-index-show', |
178 | 186 | self.__toogle_navigator_cb) |
187 | self._view_toolbar.connect('toggle-tray-show', | |
188 | self.__toogle_tray_cb) | |
179 | 189 | view_toolbar_button = ToolbarButton( |
180 | 190 | page=self._view_toolbar, |
181 | 191 | icon_name='toolbar-view') |
442 | 452 | self._toc_separator.hide() |
443 | 453 | if scrollbar_pos > -1: |
444 | 454 | self._view.set_vertical_pos(scrollbar_pos) |
455 | ||
456 | def __toogle_tray_cb(self, button, visible): | |
457 | if visible: | |
458 | logging.error('Show tray') | |
459 | self.tray.show() | |
460 | else: | |
461 | logging.error('Hide tray') | |
462 | self.tray.hide() | |
445 | 463 | |
446 | 464 | def __num_page_entry_insert_text_cb(self, entry, text, length, position): |
447 | 465 | if not re.match('[0-9]', text): |
852 | 870 | |
853 | 871 | filehash = get_md5(filepath) |
854 | 872 | self._bookmarkmanager = BookmarkManager(filehash) |
873 | self._bookmarkmanager.connect('added_bookmark', | |
874 | self._added_bookmark_cb) | |
875 | self._bookmarkmanager.connect('removed_bookmark', | |
876 | self._removed_bookmark_cb) | |
877 | ||
878 | # Add the bookmarks to the tray | |
879 | color = profile.get_color().to_string() | |
880 | owner = profile.get_nick_name() | |
881 | for bookmark in self._bookmarkmanager.get_bookmarks(): | |
882 | page = bookmark.page_no | |
883 | title = _('Page %d') % page | |
884 | thumb = self._bookmarkmanager.get_bookmark_preview(page) | |
885 | if thumb is None: | |
886 | logging.error('Preview NOT FOUND') | |
887 | thumb = self._get_screenshot() | |
888 | logging.error('Add link to tray page %d preview %s', page, thumb) | |
889 | self._add_link_totray(page, thumb, color, title, owner) | |
890 | ||
855 | 891 | self._bookmark_view.set_bookmarkmanager(self._bookmarkmanager) |
856 | 892 | self._update_toc() |
857 | 893 | self._view.connect_page_changed_handler(self.__page_changed_cb) |
1041 | 1077 | |
1042 | 1078 | def __view_toolbar_go_fullscreen_cb(self, view_toolbar): |
1043 | 1079 | self.fullscreen() |
1080 | ||
1081 | def _added_bookmark_cb(self, bookmarkmanager, page): | |
1082 | logging.error('Bookmark added page %d', page) | |
1083 | title = _('Page %d') % page | |
1084 | color = profile.get_color().to_string() | |
1085 | owner = profile.get_nick_name() | |
1086 | thumb = self._get_screenshot() | |
1087 | self._add_link_totray(page, thumb, color, title, owner) | |
1088 | bookmarkmanager.add_bookmark_preview(page - 1, thumb) | |
1089 | ||
1090 | def _removed_bookmark_cb(self, bookmarkmanager, page): | |
1091 | logging.error('Bookmark removed page %d', page) | |
1092 | # remove button from tray | |
1093 | for button in self.tray.get_children(): | |
1094 | if button.page == page: | |
1095 | self.tray.remove_item(button) | |
1096 | if len(self.tray.get_children()) == 0: | |
1097 | self.tray.hide() | |
1098 | self._view_toolbar.traybutton.props.active = False | |
1099 | ||
1100 | def _add_link_totray(self, page, buf, color, title, owner): | |
1101 | ''' add a link to the tray ''' | |
1102 | item = LinkButton(buf, color, title, owner, page) | |
1103 | item.connect('clicked', self._bookmark_button_clicked_cb, page) | |
1104 | item.connect('remove_link', self._bookmark_button_removed_cb) | |
1105 | self.tray.show() | |
1106 | self.tray.add_item(item) | |
1107 | item.show() | |
1108 | self._view_toolbar.traybutton.props.active = True | |
1109 | ||
1110 | def _bookmark_button_clicked_cb(self, button, page): | |
1111 | self._view.set_current_page(page - 1) | |
1112 | ||
1113 | def _bookmark_button_removed_cb(self, button, page): | |
1114 | self._bookmark_view.del_bookmark(page - 1) | |
1115 | ||
1116 | def _get_screenshot(self): | |
1117 | """Copied from activity.get_preview() | |
1118 | """ | |
1119 | if self.canvas is None or not hasattr(self.canvas, 'get_window'): | |
1120 | return None | |
1121 | ||
1122 | window = self.canvas.get_window() | |
1123 | alloc = self.canvas.get_allocation() | |
1124 | ||
1125 | dummy_cr = Gdk.cairo_create(window) | |
1126 | target = dummy_cr.get_target() | |
1127 | canvas_width, canvas_height = alloc.width, alloc.height | |
1128 | screenshot_surface = target.create_similar(cairo.CONTENT_COLOR, | |
1129 | canvas_width, canvas_height) | |
1130 | del dummy_cr, target | |
1131 | ||
1132 | cr = cairo.Context(screenshot_surface) | |
1133 | r, g, b, a_ = style.COLOR_PANEL_GREY.get_rgba() | |
1134 | cr.set_source_rgb(r, g, b) | |
1135 | cr.paint() | |
1136 | self.canvas.draw(cr) | |
1137 | del cr | |
1138 | ||
1139 | preview_width, preview_height = style.zoom(100), style.zoom(80) | |
1140 | preview_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, | |
1141 | preview_width, preview_height) | |
1142 | cr = cairo.Context(preview_surface) | |
1143 | ||
1144 | scale_w = preview_width * 1.0 / canvas_width | |
1145 | scale_h = preview_height * 1.0 / canvas_height | |
1146 | scale = min(scale_w, scale_h) | |
1147 | ||
1148 | translate_x = int((preview_width - (canvas_width * scale)) / 2) | |
1149 | translate_y = int((preview_height - (canvas_height * scale)) / 2) | |
1150 | ||
1151 | cr.translate(translate_x, translate_y) | |
1152 | cr.scale(scale, scale) | |
1153 | ||
1154 | cr.set_source_rgba(1, 1, 1, 0) | |
1155 | cr.set_operator(cairo.OPERATOR_SOURCE) | |
1156 | cr.paint() | |
1157 | cr.set_source_surface(screenshot_surface) | |
1158 | cr.paint() | |
1159 | ||
1160 | preview_str = StringIO.StringIO() | |
1161 | preview_surface.write_to_png(preview_str) | |
1162 | return preview_str.getvalue() |
20 | 20 | import shutil |
21 | 21 | import sqlite3 |
22 | 22 | import time |
23 | ||
23 | import base64 | |
24 | ||
25 | from gi.repository import GObject | |
24 | 26 | from sugar3 import profile |
25 | 27 | |
26 | 28 | from readbookmark import Bookmark |
74 | 76 | conn.commit() |
75 | 77 | |
76 | 78 | |
77 | class BookmarkManager: | |
79 | def _init_db_previews(conn): | |
80 | conn.execute('CREATE TABLE IF NOT EXISTS PREVIEWS ' + | |
81 | '(md5 TEXT, page INTEGER, ' + | |
82 | 'preview)') | |
83 | conn.commit() | |
84 | ||
85 | ||
86 | class BookmarkManager(GObject.GObject): | |
87 | ||
88 | __gsignals__ = { | |
89 | 'added_bookmark': (GObject.SignalFlags.RUN_FIRST, | |
90 | None, ([int])), | |
91 | 'removed_bookmark': (GObject.SignalFlags.RUN_FIRST, | |
92 | None, ([int])), | |
93 | } | |
78 | 94 | |
79 | 95 | def __init__(self, filehash): |
96 | GObject.GObject.__init__(self) | |
80 | 97 | self._filehash = filehash |
81 | 98 | |
82 | 99 | dbpath = _init_db() |
85 | 102 | |
86 | 103 | self._conn = sqlite3.connect(dbpath) |
87 | 104 | _init_db_highlights(self._conn) |
105 | _init_db_previews(self._conn) | |
88 | 106 | |
89 | 107 | self._conn.text_factory = lambda x: unicode(x, "utf-8", "ignore") |
90 | 108 | |
97 | 115 | self._color = profile.get_color().to_string() |
98 | 116 | |
99 | 117 | def add_bookmark(self, page, content, local=1): |
118 | logging.debug('add_bookmark page %d', page) | |
100 | 119 | # locale = 0 means that this is a bookmark originally |
101 | 120 | # created by the person who originally shared the file |
102 | 121 | timestamp = time.time() |
107 | 126 | self._conn.commit() |
108 | 127 | |
109 | 128 | self._resync_bookmark_cache() |
129 | self.emit('added_bookmark', page + 1) | |
110 | 130 | |
111 | 131 | def del_bookmark(self, page): |
112 | 132 | # We delete only the locally made bookmark |
113 | ||
133 | logging.debug('del_bookmark page %d', page) | |
114 | 134 | t = (self._filehash, page, self._user) |
115 | 135 | self._conn.execute('delete from bookmarks ' + \ |
116 | 136 | 'where md5=? and page=? and user=?', t) |
117 | 137 | self._conn.commit() |
118 | ||
138 | self._del_bookmark_preview(page) | |
119 | 139 | self._resync_bookmark_cache() |
140 | self.emit('removed_bookmark', page + 1) | |
141 | ||
142 | def add_bookmark_preview(self, page, preview): | |
143 | logging.debug('add_bookmark_preview page %d', page) | |
144 | t = (self._filehash, page, base64.b64encode(preview)) | |
145 | self._conn.execute('insert into previews values ' + \ | |
146 | '(?, ?, ?)', t) | |
147 | self._conn.commit() | |
148 | ||
149 | def _del_bookmark_preview(self, page): | |
150 | logging.debug('del_bookmark_preview page %d', page) | |
151 | t = (self._filehash, page) | |
152 | self._conn.execute('delete from previews ' + \ | |
153 | 'where md5=? and page=?', t) | |
154 | self._conn.commit() | |
155 | ||
156 | def get_bookmark_preview(self, page): | |
157 | logging.debug('get_bookmark page %d', page) | |
158 | rows = self._conn.execute('select preview from previews ' + \ | |
159 | 'where md5=? and page=?', (self._filehash, page)) | |
160 | for row in rows: | |
161 | return base64.b64decode(row[0]) | |
162 | return None | |
120 | 163 | |
121 | 164 | def _populate_bookmarks(self): |
122 | 165 | # TODO: Figure out if caching the entire set of bookmarks |
126 | 169 | |
127 | 170 | for row in rows: |
128 | 171 | self._bookmarks.append(Bookmark(row)) |
172 | logging.debug('loading %d bookmarks', len(self._bookmarks)) | |
173 | ||
174 | def get_bookmarks(self): | |
175 | return self._bookmarks | |
129 | 176 | |
130 | 177 | def get_bookmarks_for_page(self, page): |
131 | 178 | bookmarks = [] |
166 | 166 | ([])), |
167 | 167 | 'toggle-index-show': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, |
168 | 168 | ([bool])), |
169 | 'toggle-tray-show': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, | |
170 | ([bool])), | |
169 | 171 | } |
170 | 172 | |
171 | 173 | def __init__(self): |
222 | 224 | self._fullscreen.connect('clicked', self._fullscreen_cb) |
223 | 225 | self.insert(self._fullscreen, -1) |
224 | 226 | self._fullscreen.show() |
227 | ||
228 | self.traybutton = ToggleToolButton('tray-show') | |
229 | self.traybutton.set_icon_name('tray-favourite') | |
230 | self.traybutton.connect('toggled', self.__tray_toggled_cb) | |
231 | #self.traybutton.props.sensitive = False | |
232 | self.traybutton.props.active = False | |
233 | self.insert(self.traybutton, -1) | |
234 | self.traybutton.show() | |
225 | 235 | |
226 | 236 | self._view_notify_zoom_handler = None |
227 | 237 | |
274 | 284 | |
275 | 285 | def _fullscreen_cb(self, button): |
276 | 286 | self.emit('go-fullscreen') |
287 | ||
288 | def __tray_toggled_cb(self, button): | |
289 | self.emit('toggle-tray-show', button.get_active()) | |
290 | if button.props.active: | |
291 | self.traybutton.set_tooltip(_('Show Tray')) | |
292 | else: | |
293 | self.traybutton.set_tooltip(_('Hide Tray')) |