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
19 from gettext
import gettext
as _
29 from sugar
.activity
import activity
30 from sugar
.bundle
.bundle
import ZipExtractException
, RegistrationException
31 from sugar
.datastore
import datastore
34 from journaltoolbox
import MainToolbox
, DetailToolbox
35 from listview
import ListView
36 from detailview
import DetailView
37 from volumestoolbar
import VolumesToolbar
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
):
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:
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}
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
)
94 self
.ObjectChooserCancelled(chooser_id
)
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
102 parent
= gtk
.gdk
.window_foreign_new(parent_xid
)
105 chooser
= ObjectChooser(parent
)
106 chooser
.connect('response', self
._chooser
_response
_cb
, chooser_id
)
111 @dbus.service
.signal(J_DBUS_INTERFACE
, signature
="ss")
112 def ObjectChooserResponse(self
, chooser_id
, object_id
):
115 @dbus.service
.signal(J_DBUS_INTERFACE
, signature
="s")
116 def ObjectChooserCancelled(self
, chooser_id
):
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
)
153 self
._critical
_space
_alert
= None
154 self
._check
_available
_space
()
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
):
215 self
._detail
_toolbox
.entry_toolbar
.set_jobject(jobject
)
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()
224 self
._detail
_view
.props
.jobject
= jobject
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
)
237 self
._show
_secondary
_view
(jobject
)
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
)
250 self
._check
_for
_bundle
(jobject
)
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
)
261 self
._check
_for
_bundle
(jobject
)
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
)
280 if bundle
.is_installed():
284 except (ZipExtractException
, RegistrationException
), e
:
285 logging
.warning('Could not install bundle %s: %r' %
286 (jobject
.file_path
, e
))
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.
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
:
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
()
336 self
._critical
_space
_alert
= None