Commit from One Laptop Per Child: Translation System by user HoboPrimate. 31 of 31...
[journal-activity.git] / journalactivity.py
blob12099b6389c395c2dd3bfc086354812e115ad411
1 # Copyright (C) 2006, Red Hat, Inc.
2 # Copyright (C) 2007, One Laptop Per Child
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 import logging
19 from gettext import gettext as _
20 import sys
21 import traceback
22 import uuid
24 import gtk
25 import dbus
26 import statvfs
27 import os
29 from sugar.activity import activity
30 from sugar.bundle.bundle import ZipExtractException, RegistrationException
31 from sugar.datastore import datastore
32 from sugar import env
34 from journaltoolbox import MainToolbox, DetailToolbox
35 from listview import ListView
36 from detailview import DetailView
37 from volumestoolbar import VolumesToolbar
38 import misc
39 from journalentrybundle import JournalEntryBundle
40 from objectchooser import ObjectChooser
41 from modalalert import ModalAlert
43 DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
44 DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
45 DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
47 J_DBUS_SERVICE = 'org.laptop.Journal'
48 J_DBUS_INTERFACE = 'org.laptop.Journal'
49 J_DBUS_PATH = '/org/laptop/Journal'
51 _SPACE_TRESHOLD = 52428800
53 class JournalActivityDBusService(dbus.service.Object):
54 def __init__(self, parent):
55 self._parent = parent
56 session_bus = dbus.SessionBus()
57 bus_name = dbus.service.BusName(J_DBUS_SERVICE,
58 bus=session_bus, replace_existing=False, allow_replacement=False)
59 logging.debug('bus_name: %r', bus_name)
60 dbus.service.Object.__init__(self, bus_name, J_DBUS_PATH)
62 @dbus.service.method(J_DBUS_INTERFACE,
63 in_signature='a{sv}', out_signature='')
64 def FocusSearch(self, search_dict):
65 """Set search parameters and grab focus
66 search_dict can contain:
67 -query: string
68 -activity: string that can contain bundle id
69 -mimetype: list of strings
70 -mtime: a dict of the form:
71 {'start': 1193917107.9856629, 'end': 1193917107.9856629}
72 """
73 self._parent.present()
74 self._parent._show_main_view()
75 self._parent.set_search(search_dict)
76 self._parent.search_grab_focus()
78 @dbus.service.method(J_DBUS_INTERFACE,
79 in_signature='s', out_signature='')
80 def ShowObject(self, object_id):
81 """Pop-up journal and show object with object_id"""
83 logging.debug('Trying to show object %s', object_id)
85 if self._parent.show_object(object_id):
86 self._parent.present()
88 def _chooser_response_cb(self, chooser, response_id, chooser_id):
89 logging.debug('JournalActivityDBusService._chooser_response_cb')
90 if response_id == gtk.RESPONSE_ACCEPT:
91 object_id = chooser.get_selected_object_id()
92 self.ObjectChooserResponse(chooser_id, object_id)
93 else:
94 self.ObjectChooserCancelled(chooser_id)
95 chooser.destroy()
96 del chooser
98 @dbus.service.method(J_DBUS_INTERFACE, in_signature='i', out_signature='s')
99 def ChooseObject(self, parent_xid):
100 chooser_id = uuid.uuid4().hex
101 if parent_xid > 0:
102 parent = gtk.gdk.window_foreign_new(parent_xid)
103 else:
104 parent = None
105 chooser = ObjectChooser(parent)
106 chooser.connect('response', self._chooser_response_cb, chooser_id)
107 chooser.show()
109 return chooser_id
111 @dbus.service.signal(J_DBUS_INTERFACE, signature="ss")
112 def ObjectChooserResponse(self, chooser_id, object_id):
113 pass
115 @dbus.service.signal(J_DBUS_INTERFACE, signature="s")
116 def ObjectChooserCancelled(self, chooser_id):
117 pass
119 class JournalActivity(activity.Activity):
120 def __init__(self, handle):
121 activity.Activity.__init__(self, handle, create_jobject=False)
123 self.set_title(_('Journal'))
125 self._main_view = None
126 self._secondary_view = None
127 self._list_view = None
128 self._detail_view = None
129 self._main_toolbox = None
130 self._detail_toolbox = None
132 self._setup_main_view()
133 self._setup_secondary_view()
135 self.add_events(gtk.gdk.ALL_EVENTS_MASK | gtk.gdk.VISIBILITY_NOTIFY_MASK)
136 self.connect('visibility-notify-event',
137 self.__visibility_notify_event_cb)
138 self.connect('window-state-event', self.__window_state_event_cb)
139 self.connect('key-press-event', self._key_press_event_cb)
140 self.connect('focus-in-event', self._focus_in_event_cb)
142 bus = dbus.SessionBus()
143 data_store = dbus.Interface(
144 bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE)
145 data_store.connect_to_signal('Created', self.__data_store_created_cb)
146 data_store.connect_to_signal('Updated', self.__data_store_updated_cb)
147 data_store.connect_to_signal('Deleted', self.__data_store_deleted_cb)
149 self._dbus_service = JournalActivityDBusService(self)
151 self.iconify()
153 self._critical_space_alert = None
154 self._check_available_space()
156 def can_close(self):
157 return False
159 def _setup_main_view(self):
160 self._main_toolbox = MainToolbox()
161 self._main_view = gtk.VBox()
163 self._list_view = ListView()
164 self._list_view.connect('detail-clicked', self.__detail_clicked_cb)
165 self._main_view.pack_start(self._list_view)
166 self._list_view.show()
168 volumes_toolbar = VolumesToolbar()
169 volumes_toolbar.connect('volume-changed', self._volume_changed_cb)
170 self._main_view.pack_start(volumes_toolbar, expand=False)
172 search_toolbar = self._main_toolbox.search_toolbar
173 search_toolbar.connect('query-changed', self._query_changed_cb)
174 search_toolbar.set_volume_id(datastore.mounts()[0]['id'])
176 def _setup_secondary_view(self):
177 self._secondary_view = gtk.VBox()
179 self._detail_toolbox = DetailToolbox()
180 entry_toolbar = self._detail_toolbox.entry_toolbar
182 self._detail_view = DetailView()
183 self._detail_view.connect('go-back-clicked', self.__go_back_clicked_cb)
184 self._secondary_view.pack_end(self._detail_view)
185 self._detail_view.show()
187 def _key_press_event_cb(self, widget, event):
188 keyname = gtk.gdk.keyval_name(event.keyval)
189 logging.info(keyname)
190 logging.info(event.state)
191 if keyname == 'Escape':
192 self._show_main_view()
194 def __detail_clicked_cb(self, list_view, entry):
195 self._show_secondary_view(entry.jobject)
197 def __go_back_clicked_cb(self, detail_view):
198 self._show_main_view()
200 def _query_changed_cb(self, toolbar, query):
201 self._list_view.update_with_query(query)
202 self._show_main_view()
204 def _show_main_view(self):
205 if self.toolbox != self._main_toolbox:
206 self.set_toolbox(self._main_toolbox)
207 self._main_toolbox.show()
209 if self.canvas != self._main_view:
210 self.set_canvas(self._main_view)
211 self._main_view.show()
213 def _show_secondary_view(self, jobject):
214 try:
215 self._detail_toolbox.entry_toolbar.set_jobject(jobject)
216 except Exception:
217 logging.error('Exception while displaying entry:\n' + \
218 ''.join(traceback.format_exception(*sys.exc_info())))
220 self.set_toolbox(self._detail_toolbox)
221 self._detail_toolbox.show()
223 try:
224 self._detail_view.props.jobject = jobject
225 except Exception:
226 logging.error('Exception while displaying entry:\n' + \
227 ''.join(traceback.format_exception(*sys.exc_info())))
229 self.set_canvas(self._secondary_view)
230 self._secondary_view.show()
232 def show_object(self, object_id):
233 jobject = datastore.get(object_id)
234 if jobject is None:
235 return False
236 else:
237 self._show_secondary_view(jobject)
238 return True
240 def _volume_changed_cb(self, volume_toolbar, volume_id):
241 logging.debug('Selected volume: %r.' % volume_id)
242 self._main_toolbox.search_toolbar.set_volume_id(volume_id)
243 self._main_toolbox.set_current_toolbar(0)
245 def __data_store_created_cb(self, uid):
246 jobject = datastore.get(uid)
247 if jobject is None:
248 return
249 try:
250 self._check_for_bundle(jobject)
251 finally:
252 jobject.destroy()
253 self._main_toolbox.search_toolbar.refresh_filters()
254 self._check_available_space()
256 def __data_store_updated_cb(self, uid):
257 jobject = datastore.get(uid)
258 if jobject is None:
259 return
260 try:
261 self._check_for_bundle(jobject)
262 finally:
263 jobject.destroy()
264 self._check_available_space()
266 def __data_store_deleted_cb(self, uid):
267 if self.canvas == self._secondary_view and \
268 uid == self._detail_view.props.jobject.object_id:
269 self._show_main_view()
271 def _focus_in_event_cb(self, window, event):
272 self.search_grab_focus()
273 self._list_view.update_dates()
275 def _check_for_bundle(self, jobject):
276 bundle = misc.get_bundle(jobject)
277 if bundle is None:
278 return
280 if bundle.is_installed():
281 return
282 try:
283 bundle.install()
284 except (ZipExtractException, RegistrationException), e:
285 logging.warning('Could not install bundle %s: %r' %
286 (jobject.file_path, e))
287 return
289 if jobject.metadata['mime_type'] == JournalEntryBundle.MIME_TYPE:
290 datastore.delete(jobject.object_id)
292 def set_search(self, search_dict):
293 search_toolbar = self._main_toolbox.search_toolbar
294 if 'query' in search_dict:
295 search_toolbar._search_entry.set_text(search_dict['query'])
297 def search_grab_focus(self):
298 search_toolbar = self._main_toolbox.search_toolbar
299 search_toolbar._search_entry.grab_focus()
301 def take_screenshot(self):
302 # Don't take any screenshot. Only makes the system slower.
303 pass
305 def __window_state_event_cb(self, window, event):
306 logging.debug('window_state_event_cb %r' % self)
307 if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
308 visible = not event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED
309 self._list_view.set_is_visible(visible)
311 def __visibility_notify_event_cb(self, window, event):
312 logging.debug('visibility_notify_event_cb %r' % self)
313 visible = event.state != gtk.gdk.VISIBILITY_FULLY_OBSCURED
314 self._list_view.set_is_visible(visible)
316 def _check_available_space(self):
317 ''' Check available space on device
319 If the available space is below 50MB an alert will be
320 shown which encourages to delete old journal entries.
323 if self._critical_space_alert:
324 return
325 stat = os.statvfs(env.get_profile_path())
326 free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
327 if free_space < _SPACE_TRESHOLD:
328 self._critical_space_alert = ModalAlert()
329 self._critical_space_alert.connect('destroy',
330 self.__alert_closed_cb)
331 self._critical_space_alert.show()
333 def __alert_closed_cb(self, data):
334 self._show_main_view()
335 self.present()
336 self._critical_space_alert = None