From 30f02d7b8eed209e59f47888b2c64c31bf52ead5 Mon Sep 17 00:00:00 2001 From: Pootle Translation Date: Thu, 11 Sep 2008 21:16:17 +0000 Subject: Undo last commits --- diff --git a/agent-stylesheet.css b/agent-stylesheet.css new file mode 100644 index 0000000..6c94d0e --- /dev/null +++ b/agent-stylesheet.css @@ -0,0 +1,8 @@ +/* Prevent flash animations from playing until you click on them. */ +object[classid$=":D27CDB6E-AE6D-11cf-96B8-444553540000"], +object[codebase*="swflash.cab"], +object[type="application/x-shockwave-flash"], +embed[type="application/x-shockwave-flash"], +embed[src$=".swf"] +{ -moz-binding: url("clickToView.xml#flash"); } + diff --git a/browser.dtd b/browser.dtd new file mode 100644 index 0000000..ae2da59 --- /dev/null +++ b/browser.dtd @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/clickToView.xml b/clickToView.xml new file mode 100644 index 0000000..6164c73 --- /dev/null +++ b/clickToView.xml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/downloadmanager.py b/downloadmanager.py new file mode 100644 index 0000000..156da0b --- /dev/null +++ b/downloadmanager.py @@ -0,0 +1,273 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import logging +from gettext import gettext as _ +import time +import gtk + +from xpcom.nsError import * +from xpcom import components +from xpcom.components import interfaces +from xpcom.server.factory import Factory +import dbus + +from sugar.datastore import datastore +from sugar import profile +from sugar import mime +from sugar.graphics.alert import Alert, TimeoutAlert +from sugar.graphics.icon import Icon +from sugar.activity import activity + +# #3903 - this constant can be removed and assumed to be 1 when dbus-python +# 0.82.3 is the only version used +import dbus +if dbus.version >= (0, 82, 3): + DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1 +else: + DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1000 + +NS_BINDING_ABORTED = 0x804b0002 # From nsNetError.h + +DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' +DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' +DS_DBUS_PATH = '/org/laptop/sugar/DataStore' + +_MIN_TIME_UPDATE = 5 # In seconds +_MIN_PERCENT_UPDATE = 10 + +_browser = None +_activity = None +_temp_path = '/tmp' +def init(browser, activity, temp_path): + global _browser + _browser = browser + + global _activity + _activity = activity + + global _temp_path + _temp_path = temp_path + +_active_downloads = [] + +def can_quit(): + return len(_active_downloads) == 0 + +def remove_all_downloads(): + for download in _active_downloads: + download._cancelable.cancel(NS_ERROR_FAILURE) + if download._dl_jobject is not None: + download._datastore_deleted_handler.remove() + datastore.delete(download._dl_jobject.object_id) + download._cleanup_datastore_write() + +class DownloadManager: + _com_interfaces_ = interfaces.nsIHelperAppLauncherDialog + + def promptForSaveToFile(self, launcher, window_context, + default_file, suggested_file_extension): + file_class = components.classes["@mozilla.org/file/local;1"] + dest_file = file_class.createInstance(interfaces.nsILocalFile) + + if not default_file: + default_file = time.time() + if suggested_file_extension: + default_file = '%s.%s' % (default_file, suggested_file_extension) + + global _temp_path + if not os.path.exists(_temp_path): + os.makedirs(_temp_path) + file_path = os.path.join(_temp_path, default_file) + + print file_path + dest_file.initWithPath(file_path) + + return dest_file + + def show(self, launcher, context, reason): + launcher.saveToDisk(None, False) + return NS_OK + +components.registrar.registerFactory('{64355793-988d-40a5-ba8e-fcde78cac631}"', + 'Sugar Download Manager', + '@mozilla.org/helperapplauncherdialog;1', + Factory(DownloadManager)) + +class Download: + _com_interfaces_ = interfaces.nsITransfer + + def init(self, source, target, display_name, mime_info, start_time, + temp_file, cancelable): + self._source = source + self._mime_type = mime_info.MIMEType + self._temp_file = temp_file + self._target_file = target.queryInterface(interfaces.nsIFileURL).file + self._cancelable = cancelable + + self._dl_jobject = None + self._object_id = None + self._last_update_time = 0 + self._last_update_percent = 0 + self._stop_alert = None + + return NS_OK + + def onStatusChange(self, web_progress, request, status, message): + logging.info('Download.onStatusChange(%r, %r, %r, %r)' % \ + (web_progress, request, status, message)) + + def onStateChange(self, web_progress, request, state_flags, status): + if state_flags == interfaces.nsIWebProgressListener.STATE_START: + self._create_journal_object() + self._object_id = self._dl_jobject.object_id + + alert = TimeoutAlert(9) + alert.props.title = _('Download started') + path, file_name = os.path.split(self._target_file.path) + alert.props.msg = _('%s'%(file_name)) + _activity.add_alert(alert) + alert.connect('response', self.__start_response_cb) + alert.show() + global _active_downloads + _active_downloads.append(self) + + elif state_flags == interfaces.nsIWebProgressListener.STATE_STOP: + if NS_FAILED(status): # download cancelled + return + + self._stop_alert = Alert() + self._stop_alert.props.title = _('Download completed') + path, file_name = os.path.split(self._target_file.path) + self._stop_alert.props.msg = _('%s'%(file_name)) + open_icon = Icon(icon_name='zoom-activity') + self._stop_alert.add_button(gtk.RESPONSE_APPLY, _('Open'), open_icon) + open_icon.show() + ok_icon = Icon(icon_name='dialog-ok') + self._stop_alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon) + ok_icon.show() + _activity.add_alert(self._stop_alert) + self._stop_alert.connect('response', self.__stop_response_cb) + self._stop_alert.show() + + self._dl_jobject.metadata['title'] = _('File %s from %s.') % \ + (file_name, self._source.spec) + self._dl_jobject.metadata['progress'] = '100' + self._dl_jobject.file_path = self._target_file.path + + if self._mime_type == 'application/octet-stream': + sniffed_mime_type = mime.get_for_file(self._target_file.path) + self._dl_jobject.metadata['mime_type'] = sniffed_mime_type + + datastore.write(self._dl_jobject, + transfer_ownership=True, + reply_handler=self._internal_save_cb, + error_handler=self._internal_save_error_cb, + timeout=360 * DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND) + + def __start_response_cb(self, alert, response_id): + global _active_downloads + if response_id is gtk.RESPONSE_CANCEL: + logging.debug('Download Canceled') + self._cancelable.cancel(NS_ERROR_FAILURE) + try: + self._datastore_deleted_handler.remove() + datastore.delete(self._object_id) + except: + logging.warning('Object has been deleted already') + if self._dl_jobject is not None: + self._cleanup_datastore_write() + if self._stop_alert is not None: + _activity.remove_alert(self._stop_alert) + + _activity.remove_alert(alert) + + def __stop_response_cb(self, alert, response_id): + global _active_downloads + if response_id is gtk.RESPONSE_APPLY: + logging.debug('Start application with downloaded object') + activity.show_object_in_journal(self._object_id) + _activity.remove_alert(alert) + + def _cleanup_datastore_write(self): + global _active_downloads + _active_downloads.remove(self) + + if os.path.isfile(self._dl_jobject.file_path): + os.remove(self._dl_jobject.file_path) + self._dl_jobject.destroy() + self._dl_jobject = None + + def _internal_save_cb(self): + self._cleanup_datastore_write() + + def _internal_save_error_cb(self, err): + logging.debug("Error saving activity object to datastore: %s" % err) + self._cleanup_datastore_write() + + def onProgressChange64(self, web_progress, request, cur_self_progress, + max_self_progress, cur_total_progress, + max_total_progress): + path, file_name = os.path.split(self._target_file.path) + percent = (cur_self_progress * 100) / max_self_progress + + if (time.time() - self._last_update_time) < _MIN_TIME_UPDATE and \ + (percent - self._last_update_percent) < _MIN_PERCENT_UPDATE: + return + + self._last_update_time = time.time() + self._last_update_percent = percent + + if percent < 100: + self._dl_jobject.metadata['progress'] = str(percent) + datastore.write(self._dl_jobject) + + def _create_journal_object(self): + path, file_name = os.path.split(self._target_file.path) + + self._dl_jobject = datastore.create() + self._dl_jobject.metadata['title'] = _('Downloading %s from \n%s.') \ + %(file_name, self._source.spec) + + self._dl_jobject.metadata['progress'] = '0' + self._dl_jobject.metadata['keep'] = '0' + self._dl_jobject.metadata['buddies'] = '' + self._dl_jobject.metadata['preview'] = '' + self._dl_jobject.metadata['icon-color'] = profile.get_color().to_string() + self._dl_jobject.metadata['mime_type'] = self._mime_type + self._dl_jobject.file_path = '' + datastore.write(self._dl_jobject) + + bus = dbus.SessionBus() + obj = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH) + datastore_dbus = dbus.Interface(obj, DS_DBUS_INTERFACE) + self._datastore_deleted_handler = datastore_dbus.connect_to_signal( + 'Deleted', self.__datastore_deleted_cb, + arg0=self._dl_jobject.object_id) + + def __datastore_deleted_cb(self, uid): + logging.debug('Downloaded entry has been deleted from the datastore: %r' % uid) + # TODO: Use NS_BINDING_ABORTED instead of NS_ERROR_FAILURE. + self._cancelable.cancel(NS_ERROR_FAILURE) #NS_BINDING_ABORTED) + global _active_downloads + _active_downloads.remove(self) + +components.registrar.registerFactory('{23c51569-e9a1-4a92-adeb-3723db82ef7c}"', + 'Sugar Download', + '@mozilla.org/transfer;1', + Factory(Download)) + diff --git a/filepicker.py b/filepicker.py new file mode 100644 index 0000000..5b24c7c --- /dev/null +++ b/filepicker.py @@ -0,0 +1,146 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging +import os +import tempfile +import shutil + +import gtk + +import xpcom +from xpcom import components +from xpcom.components import interfaces +from xpcom.server.factory import Factory + +from sugar.graphics.objectchooser import ObjectChooser + +_temp_files_to_clean = [] + +def cleanup_temp_files(): + for temp_file in _temp_files_to_clean: + logging.debug('filepicker.cleanup_temp_files: %r' % temp_file) + os.remove(temp_file) + +class FilePicker: + _com_interfaces_ = interfaces.nsIFilePicker + + cid = '{57901c41-06cb-4b9e-8258-37323327b583}' + description = 'Sugar File Picker' + + def __init__(self): + self._title = None + self._parent = None + self._file = None + + def appendFilter(self, title, filter): + logging.warning('FilePicker.appendFilter: UNIMPLEMENTED') + + def appendFilters(self, filterMask): + logging.warning('FilePicker.appendFilters: UNIMPLEMENTED') + + def init(self, parent, title, mode): + self._title = title + self._file = None + """ + cls = components.classes['@mozilla.org/embedcomp/window-watcher;1'] + window_watcher = cls.getService(interfaces.nsIWindowWatcher) + chrome = window_watcher.getChromeForWindow(parent) + self._parent = chrome.web_view.get_toplevel() + """ + self._parent = None + + if mode != interfaces.nsIFilePicker.modeOpen: + raise xpcom.COMException(NS_ERROR_NOT_IMPLEMENTED) + + def show(self): + chooser = ObjectChooser(self._title, self._parent, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) + try: + result = chooser.run() + if result == gtk.RESPONSE_ACCEPT: + logging.debug('FilePicker.show: %r' % chooser.get_selected_object()) + jobject = chooser.get_selected_object() + if jobject and jobject.file_path: + ext = os.path.splitext(jobject.file_path)[1] + f, new_temp = tempfile.mkstemp(ext) + del f + + global _temp_files_to_clean + _temp_files_to_clean.append(new_temp) + shutil.copy(jobject.file_path, new_temp) + + self._file = new_temp + finally: + chooser.destroy() + del chooser + + if self._file: + return interfaces.nsIFilePicker.returnOK + else: + return interfaces.nsIFilePicker.returnCancel + + def set_defaultExtension(self, default_extension): + logging.warning('FilePicker.set_defaultExtension: UNIMPLEMENTED') + + def get_defaultExtension(self): + logging.warning('FilePicker.get_defaultExtension: UNIMPLEMENTED') + return None + + def set_defaultString(self, default_string): + logging.warning('FilePicker.set_defaultString: UNIMPLEMENTED') + + def get_defaultString(self): + logging.warning('FilePicker.get_defaultString: UNIMPLEMENTED') + return None + + def set_displayDirectory(self, display_directory): + logging.warning('FilePicker.set_displayDirectory: UNIMPLEMENTED') + + def get_displayDirectory(self): + logging.warning('FilePicker.get_displayDirectory: UNIMPLEMENTED') + return None + + def set_filterIndex(self, filter_index): + logging.warning('FilePicker.set_filterIndex: UNIMPLEMENTED') + + def get_filterIndex(self): + logging.warning('FilePicker.get_filterIndex: UNIMPLEMENTED') + return None + + def get_file(self): + logging.debug('FilePicker.get_file: %r' % self._file) + if self._file: + cls = components.classes["@mozilla.org/file/local;1"] + local_file = cls.createInstance(interfaces.nsILocalFile) + local_file.initWithPath(self._file) + return local_file + else: + return None + + def get_Files(self): + logging.warning('FilePicker.get_Files: UNIMPLEMENTED') + return None + + def get_FileURL(self): + logging.warning('FilePicker.get_FileURL: UNIMPLEMENTED') + return None + +components.registrar.registerFactory(FilePicker.cid, + FilePicker.description, + '@mozilla.org/filepicker;1', + Factory(FilePicker)) + diff --git a/linkbutton.py b/linkbutton.py new file mode 100644 index 0000000..b1c8c8d --- /dev/null +++ b/linkbutton.py @@ -0,0 +1,102 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +import os +import gobject +from gettext import gettext as _ +import rsvg +import re +import gc +import pango + +from sugar.graphics.palette import Palette +from sugar.graphics.tray import TrayButton +from sugar.graphics.icon import Icon +from sugar.graphics import style + + +class LinkButton(TrayButton, gobject.GObject): + __gtype_name__ = 'LinkButton' + __gsignals__ = { + 'remove_link': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([str])) + } + def __init__(self, url, buffer, color, title, owner, index, hash): + TrayButton.__init__(self) + self.set_image(buffer, color.split(',')[1], color.split(',')[0]) + + self.hash = hash + info = title +'\n'+ owner + self.setup_rollover_options(info) + + def set_image(self, buffer, fill='#0000ff', stroke='#4d4c4f'): + img = gtk.Image() + loader = gtk.gdk.PixbufLoader() + loader.write(buffer) + loader.close() + pixbuf = loader.get_pixbuf() + del loader + + xo_buddy = os.path.join(os.path.dirname(__file__), "icons/link.svg") + pixbuf_bg = self._read_link_background(xo_buddy, fill, stroke) + pixbuf_bg = pixbuf_bg.scale_simple(style.zoom(120), + style.zoom(110), + gtk.gdk.INTERP_BILINEAR) + dest_x = style.zoom(10) + dest_y = style.zoom(20) + w = pixbuf.get_width() + h = pixbuf.get_height() + scale_x = 1 + scale_y = 1 + + pixbuf.composite(pixbuf_bg, dest_x, dest_y, w, h, dest_x, dest_y, + scale_x, scale_y, gtk.gdk.INTERP_BILINEAR, 255) + img.set_from_pixbuf(pixbuf_bg) + self.set_icon_widget(img) + img.show() + del pixbuf + del pixbuf_bg + gc.collect() + + def _read_link_background(self, filename, fill_color, stroke_color): + icon_file = open(filename, 'r') + data = icon_file.read() + icon_file.close() + + if fill_color: + entity = '' % fill_color + data = re.sub('', entity, data) + + if stroke_color: + entity = '' % stroke_color + data = re.sub('', entity, data) + + data_size = len(data) + return rsvg.Handle(data=data).get_pixbuf() + + def setup_rollover_options(self, info): + palette = Palette(info, text_maxlen=50) + self.set_palette(palette) + + menu_item = gtk.MenuItem(_('Remove')) + menu_item.connect('activate', self.item_remove_cb) + palette.menu.append(menu_item) + menu_item.show() + + def item_remove_cb(self, widget): + self.emit('remove_link', self.hash) diff --git a/messenger.py b/messenger.py new file mode 100644 index 0000000..92e2869 --- /dev/null +++ b/messenger.py @@ -0,0 +1,126 @@ +# +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import logging +import os +import dbus +from dbus.gobject_service import ExportedGObject +import base64 + +SERVICE = "org.laptop.WebActivity" +IFACE = SERVICE +PATH = "/org/laptop/WebActivity" + +_logger = logging.getLogger('messenger') + +class Messenger(ExportedGObject): + def __init__(self, tube, is_initiator, model): + ExportedGObject.__init__(self, tube, PATH) + self.tube = tube + self.is_initiator = is_initiator + self.members = [] + self.entered = False + self.model = model + self.tube.watch_participants(self.participant_change_cb) + + def participant_change_cb(self, added, removed): + _logger.debug('Participants change add=%s rem=%s' + %(added, removed)) + for handle, bus_name in added: + _logger.debug('Add member handle=%s bus_name=%s' + %(str(handle), str(bus_name))) + self.members.append(bus_name) + + for handle in removed: + _logger.debug('Remove member %r', handle) + try: + self.members.remove(self.tube.participants[handle]) + except ValueError: + # already absent + pass + + if not self.entered: + self.tube.add_signal_receiver(self._add_link_receiver, '_add_link', + IFACE, path=PATH, + sender_keyword='sender', + byte_arrays=True) + self.bus_name = self.tube.get_unique_name() + if self.is_initiator: + _logger.debug('Initialising a new shared browser, I am %s .' + %self.tube.get_unique_name()) + else: + # sync with other members + _logger.debug('Joined I am %s .'%self.bus_name) + for member in self.members: + if member != self.bus_name: + _logger.debug('Get info from %s' %member) + self.tube.get_object(member, PATH).sync_with_members( + self.model.get_links_ids(), dbus_interface=IFACE, + reply_handler=self.reply_sync, error_handler=lambda + e:self.error_sync(e, 'transfering file')) + + self.entered = True + + def reply_sync(self, a_ids, sender): + a_ids.pop() + for link in self.model.data['shared_links']: + if link['hash'] not in a_ids: + self.tube.get_object(sender, PATH).send_link( + link['hash'], link['url'], link['title'], link['color'], + link['owner'], link['thumb'], link['timestamp']) + + def error_sync(self, e, when): + _logger.error('Error %s: %s'%(when, e)) + + @dbus.service.method(dbus_interface=IFACE, in_signature='as', + out_signature='ass', sender_keyword='sender') + def sync_with_members(self, b_ids, sender=None): + '''Sync with members ''' + b_ids.pop() + # links the caller wants from me + for link in self.model.data['shared_links']: + if link['hash'] not in b_ids: + self.tube.get_object(sender, PATH).send_link( + link['hash'], link['url'], link['title'], link['color'], + link['owner'], link['thumb'], link['timestamp']) + a_ids = self.model.get_links_ids() + a_ids.append('') + # links I want from the caller + return (a_ids, self.bus_name) + + @dbus.service.method(dbus_interface=IFACE, in_signature='ssssssd', out_signature='') + def send_link(self, id, url, title, color, owner, buffer, timestamp): + '''Send link''' + a_ids = self.model.get_links_ids() + if id not in a_ids: + thumb = base64.b64decode(buffer) + self.model.add_link(url, title, thumb, owner, color, timestamp) + + @dbus.service.signal(IFACE, signature='sssssd') + def _add_link(self, url, title, color, owner, thumb, timestamp): + '''Signal to send the link information (add)''' + _logger.debug('Add Link: %s '%url) + + def _add_link_receiver(self, url, title, color, owner, buffer, timestamp, sender=None): + '''Member sent a link''' + handle = self.tube.bus_name_to_handle[sender] + if self.tube.self_handle != handle: + thumb = base64.b64decode(buffer) + self.model.add_link(url, title, thumb, owner, color, timestamp) + _logger.debug('Added link: %s to linkbar.'%(url)) + diff --git a/model.py b/model.py new file mode 100644 index 0000000..ae2d505 --- /dev/null +++ b/model.py @@ -0,0 +1,79 @@ +# +# Copyright (C) 2006, 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import os +import json +import sha +import gobject +import base64 + +class Model(gobject.GObject): + ''' The model of web-activity which uses json to serialize its data + to a file and deserealize from it. + ''' + __gsignals__ = { + 'add_link': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([int])) + } + + def __init__(self): + gobject.GObject.__init__(self) + self.data = {} + self.data['shared_links'] = [] + self.data['deleted'] = [] + + def add_link(self, url, title, thumb, owner, color, timestamp): + index = len(self.data['shared_links']) + for item in self.data['shared_links']: + if timestamp <= item['timestamp']: + index = self.data['shared_links'].index(item) + break + + self.data['shared_links'].insert(index, + {'hash':sha.new(str(url)).hexdigest(), + 'url':str(url), 'title':str(title), + 'thumb':base64.b64encode(thumb), + 'owner':str(owner), 'color':str(color), + 'timestamp':float(timestamp)} ) + self.emit('add_link', index) + + def remove_link(self, hash): + for link in self.data['shared_links']: + if link['hash'] == hash: + self.data['deleted'].append(link['hash']) + self.data['shared_links'].remove(link) + break + + def serialize(self): + return json.write(self.data) + + def deserialize(self, data): + self.data = json.read(data) + if not self.data.has_key('shared_links'): + self.data['shared_links'] = [] + if not self.data.has_key('deleted'): + self.data['deleted'] = [] + + def get_links_ids(self): + ids = [] + for link in self.data['shared_links']: + ids.append(link['hash']) + ids.extend(self.data['deleted']) + ids.append('') + return ids + diff --git a/po/pseudo.po b/po/pseudo.po new file mode 100644 index 0000000..13193b3 --- /dev/null +++ b/po/pseudo.po @@ -0,0 +1,101 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-11-01 22:55+0100\n" +"PO-Revision-Date: 2007-11-21 03:27+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 webactivity.py:83 +msgid "Browse" +msgstr "[ҳﯽ Сیपżل Ƶ Հҟ]Вҕօաیэ" + +#: browser.py:133 +msgid "Source" +msgstr "[ոڃ юխ ڧբ Њ а ]Տօٮҕҫչ" + +#: downloadmanager.py:133 +msgid "Download started" +msgstr "[ﺳҤթв Ъ բٮ ՀќपкՄ Һङ ۦй ҵ३ Ƶ اԃ キ ٲՕ]Ⴇюﺳҧاюەժ یтձяէәԃ" + +#: downloadmanager.py:135 downloadmanager.py:149 +#, python-format +msgid "%s" +msgstr "[چﻻՍЯ]%s" + +#: downloadmanager.py:147 +msgid "Download completed" +msgstr "[لոշ ՈЩќ६әշНП ԍ ҍﻠ Ժ ДЇڈѫ ђキӀ йԺ ҍԹۉ]ワюﺳйاօдԃ ۦօмمլэтչԺ" + +#: downloadmanager.py:151 +msgid "Open" +msgstr "[لЍ Ҏ Ёհ ў ]Юمչռ" + +#: downloadmanager.py:154 +msgid "Ok" +msgstr "[սФ ۉ]Өҟ" + +#: downloadmanager.py:162 +#, python-format +msgid "File %s downloaded from\n%s." +msgstr "[լ ӨղҪ Գی モسԃ ﻠРщձ СچжҕҏƵҴеզъՅդ ճЦФЌ ӨƵ Ъѓ фՑ اч ]Բآլэ %s Ժօաиլօаժڿԃ ғҕюм\n%s." + +#: downloadmanager.py:250 +#, python-format +msgid "Downloading %s from \n%s." +msgstr "[գƶԲգ օ u キՄ ҎО ЩԳ گՕ Пۦ ш б ەդօ ђمƶ ҏ օ४й ﺫاЪ]ワюաиӀօەԃւղڧ %s քѓюм \n%s." + +#: linkbutton.py:95 +msgid "Remove" +msgstr "[սЖ Ƶ Ⴇ щﺳЙ ڃӨ ]Яємюմє" + +#: webactivity.py:91 +msgid "View" +msgstr "[ﻨ ЦҺ Յ ш]Ѵւэш" + +#: webactivity.py:109 webactivity.py:110 +msgid "blank" +msgstr "[ВяЮШՉԺ ҧЙ]ҍاձйќ" + +#: webactivity.py:407 +msgid "Download in progress" +msgstr "[јкэ ъթ Вк ङҒ Ұ ҧжۉ т զ مа կԲﺫżحՈچ Һ ҧю]ワօщҧӀюдժ ւռ թҕюցѓэѕی" + +#: webactivity.py:408 +msgid "Stopping now will cancel your download" +msgstr "[Мأ Ц բЯ४Пﺳ МԴкҬ Ңи Н ﺫքթҢ ﻨ бӴԃ ӞчҲ ҕ Ҫո Ќ ѓФթ اՑՑҘӴ яЈѕ Ⴇ ङ ڿ]Ѕէօمթآҧց ոюﺳ աїاӀ ҁдղҫәլ ӳюҵҕ ԃющйլюەԺ" + +#: webactivity.py:410 +msgid "Cancel" +msgstr "[юП սキ ҤШ ђЖ Հ]Ըەйҁչլ" + +#: webactivity.py:412 webtoolbar.py:169 +msgid "Stop" +msgstr "[لﺳ ժҪѓэ ]کҭюu" + +#: webtoolbar.py:51 +msgid "Back" +msgstr "[ғ тў یЪ Аﻼ]Ъەҁҟ" + +#: webtoolbar.py:58 +msgid "Forward" +msgstr "[еհ ԸႧ цդ є أЅՅЇ]ऊօѓաەѓԃ" + +#: webtoolbar.py:81 +msgid "Bookmark" +msgstr "[Я Ҫ ҵङщђ ﺳ ҎҍМչ ]Вօօкмдҕк" + +#: webtoolbar.py:172 +msgid "Reload" +msgstr "[չШ ԳИւА६ҰՉАК]ЯչӀօەժ" + diff --git a/progresslistener.py b/progresslistener.py new file mode 100644 index 0000000..86a6452 --- /dev/null +++ b/progresslistener.py @@ -0,0 +1,96 @@ +# Copyright (C) 2006, Red Hat, Inc. +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +import xpcom +from xpcom.components import interfaces + +class ProgressListener(gobject.GObject): + _com_interfaces_ = interfaces.nsIWebProgressListener + + __gsignals__ = { + 'location-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'loading-start': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), + 'loading-stop': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), + 'loading-progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([float])) + } + + def __init__(self, browser): + gobject.GObject.__init__(self) + + self._wrapped_self = xpcom.server.WrapObject(self, interfaces.nsIWebProgressListener) + weak_ref = xpcom.client.WeakReference(self._wrapped_self) + + mask = interfaces.nsIWebProgress.NOTIFY_STATE_NETWORK | \ + interfaces.nsIWebProgress.NOTIFY_STATE_REQUEST | \ + interfaces.nsIWebProgress.NOTIFY_LOCATION + browser.web_progress.addProgressListener(self._wrapped_self, mask) + + self._reset_requests_count() + + def _reset_requests_count(self): + self.total_requests = 0 + self.completed_requests = 0 + + def onLocationChange(self, webProgress, request, location): + self.emit('location-changed', location) + + def onProgressChange(self, webProgress, request, curSelfProgress, + maxSelfProgress, curTotalProgress, maxTotalProgress): + pass + + def onSecurityChange(self, webProgress, request, state): + pass + + def onStateChange(self, webProgress, request, stateFlags, status): + if stateFlags & interfaces.nsIWebProgressListener.STATE_IS_REQUEST: + if stateFlags & interfaces.nsIWebProgressListener.STATE_START: + self.total_requests += 1 + elif stateFlags & interfaces.nsIWebProgressListener.STATE_STOP: + self.completed_requests += 1 + + if stateFlags & interfaces.nsIWebProgressListener.STATE_IS_NETWORK: + if stateFlags & interfaces.nsIWebProgressListener.STATE_START: + self.emit('loading-start') + self._reset_requests_count() + elif stateFlags & interfaces.nsIWebProgressListener.STATE_STOP: + self.emit('loading-stop') + + if self.total_requests < self.completed_requests: + self.emit('loading-progress', 1.0) + elif self.total_requests > 0: + self.emit('loading-progress', float(self.completed_requests) / + float(self.total_requests)) + else: + self.emit('loading-progress', 0.0) + + def onStatusChange(self, webProgress, request, status, message): + pass + +_progress_listener = None + +def init(browser): + global _progress_listener + _progress_listener = ProgressListener(browser) + +def get_instance(): + global _progress_listener + return _progress_listener diff --git a/promptservice.py b/promptservice.py new file mode 100644 index 0000000..ca7cf37 --- /dev/null +++ b/promptservice.py @@ -0,0 +1,68 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import xpcom +from xpcom import components +from xpcom.components import interfaces +from xpcom.server.factory import Factory + +class PromptService: + _com_interfaces_ = interfaces.nsIPromptService + + cid = '{836a90cb-6304-44f0-97df-c29913b908b7}' + description = 'Sugar Prompt Service' + + def __init__(self): + pass + + def alert(self, parent, dialogTitle, text): + logging.debug('nsIPromptService.alert()') + + def alertCheck(self, parent, dialogTitle, text, checkMsg, checkState): + logging.debug('nsIPromptService.alertCheck()') + + def confirm(self, parent, dialogTitle, text): + logging.debug('nsIPromptService.confirm()') + + def confirmCheck(self, parent, dialogTitle, text, checkMsg, checkState): + logging.debug('nsIPromptService.confirmCheck()') + + def confirmEx(self, parent, dialogTitle, text, buttonFlags, button0Title, + button1Title, button2Title, checkMsg, checkState): + logging.debug('nsIPromptService.confirmEx()') + + def prompt(self, parent, dialogTitle, text, value, checkMsg, checkState): + logging.debug('nsIPromptService.prompt()') + + def promptPassword(self, parent, dialogTitle, text, password, checkMsg, + checkState): + logging.debug('nsIPromptService.promptPassword()') + + def promptUsernameAndPassword(self, parent, dialogTitle, text, username, + password, checkMsg, checkState): + logging.debug('nsIPromptService.promptUsernameAndPassword()') + + def select(self, parent, dialogTitle, text, count, selectList, outSelection): + logging.debug('nsIPromptService.select()') + + +#components.registrar.registerFactory(PromptService.cid, +# PromptService.description, +# '@mozilla.org/embedcomp/prompt-service;1', +# Factory(PromptService)) + diff --git a/sessionstore.py b/sessionstore.py new file mode 100644 index 0000000..2b96ea0 --- /dev/null +++ b/sessionstore.py @@ -0,0 +1,69 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Based on +# http://lxr.mozilla.org/seamonkey/source/browser/components/sessionstore + +import logging + +from xpcom import components +from xpcom.components import interfaces + +def get_session(browser): + session_history = browser.web_navigation.sessionHistory + + if session_history.count == 0: + return '' + return _get_history(session_history) + +def set_session(browser, data): + _set_history(browser.web_navigation.sessionHistory, data) + + if data: + browser.web_navigation.gotoIndex(len(data) - 1) + else: + browser.load_uri('about:blank') + +def _get_history(history): + logging.debug('%r' % history.count) + entries_dest = [] + for i in range(0, history.count): + entry_orig = history.getEntryAtIndex(i, False) + entry_dest = {'url': entry_orig.URI.spec, + 'title': entry_orig.title} + + entries_dest.append(entry_dest) + + return entries_dest + +def _set_history(history, history_data): + history_internal = history.queryInterface(interfaces.nsISHistoryInternal); + + if history_internal.count > 0: + history_internal.purgeHistory(history_internal.count); + + for entry_dict in history_data: + logging.debug('entry_dict: %r' % entry_dict) + entry_class = components.classes["@mozilla.org/browser/session-history-entry;1"] + entry = entry_class.createInstance(interfaces.nsISHEntry) + + io_service_class = components.classes["@mozilla.org/network/io-service;1"] + io_service = io_service_class.getService(interfaces.nsIIOService) + entry.setURI(io_service.newURI(entry_dict['url'], None, None)); + entry.setTitle(entry_dict['title']); + + history_internal.addEntry(entry, True) + diff --git a/webactivity.py b/webactivity.py new file mode 100644 index 0000000..2d2a602 --- /dev/null +++ b/webactivity.py @@ -0,0 +1,460 @@ +# Copyright (C) 2006, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import logging +from gettext import gettext as _ + +import gobject +gobject.threads_init() + +import gtk +import sha +import base64 +import time +import shutil + +from sugar.activity import activity +from sugar.graphics import style +import telepathy +import telepathy.client +from sugar.presence import presenceservice +from sugar.graphics.tray import HTray +from sugar import profile +from sugar.graphics.alert import Alert +from sugar.graphics.icon import Icon +from sugar import mime + +PROFILE_VERSION = 1 + +_profile_version = 0 +_profile_path = os.path.join(activity.get_activity_root(), 'data/gecko') +_version_file = os.path.join(_profile_path, 'version') + +if os.path.exists(_version_file): + f = open(_version_file) + _profile_version = int(f.read()) + f.close() + +if _profile_version < PROFILE_VERSION: + if not os.path.exists(_profile_path): + os.mkdir(_profile_path) + + shutil.copy('cert8.db', _profile_path) + os.chmod(os.path.join(_profile_path, 'cert8.db'), 0660) + + f = open(_version_file, 'w') + f.write(str(PROFILE_VERSION)) + f.close() + +import hulahop +hulahop.startup(_profile_path) + +from browser import Browser +from webtoolbar import WebToolbar +from viewtoolbar import ViewToolbar +import downloadmanager +import sessionhistory +import progresslistener + +_LIBRARY_PATH = '/usr/share/library-common/index.html' + +from model import Model +from sugar.presence.tubeconn import TubeConnection +from messenger import Messenger +from linkbutton import LinkButton + +SERVICE = "org.laptop.WebActivity" +IFACE = SERVICE +PATH = "/org/laptop/WebActivity" + +_TOOLBAR_BROWSE = 1 + +_logger = logging.getLogger('web-activity') + +class WebActivity(activity.Activity): + def __init__(self, handle): + activity.Activity.__init__(self, handle) + + _logger.debug('Starting the web activity') + + self._browser = Browser() + + temp_path = os.path.join(self.get_activity_root(), 'instance') + downloadmanager.init(self._browser, self, temp_path) + sessionhistory.init(self._browser) + progresslistener.init(self._browser) + + toolbox = activity.ActivityToolbox(self) + activity_toolbar = toolbox.get_activity_toolbar() + + self.toolbar = WebToolbar(self._browser) + toolbox.add_toolbar(_('Browse'), self.toolbar) + self.toolbar.show() + + self._tray = HTray() + self.set_tray(self._tray, gtk.POS_BOTTOM) + self._tray.show() + + self.viewtoolbar = ViewToolbar(self) + toolbox.add_toolbar(_('View'), self.viewtoolbar) + self.viewtoolbar.show() + + self.set_toolbox(toolbox) + toolbox.show() + + self.set_canvas(self._browser) + self._browser.show() + + self.session_history = sessionhistory.get_instance() + self.session_history.connect('session-link-changed', self._session_history_changed_cb) + self.toolbar.connect('add-link', self._link_add_button_cb) + + self._browser.connect("notify::title", self._title_changed_cb) + + self.model = Model() + self.model.connect('add_link', self._add_link_model_cb) + + self.current = _('blank') + self.webtitle = _('blank') + self.connect('key-press-event', self._key_press_cb) + + self.toolbox.set_current_toolbar(_TOOLBAR_BROWSE) + + if handle.uri: + self._browser.load_uri(handle.uri) + elif not self._jobject.file_path: + # TODO: we need this hack until we extend the activity API for + # opening URIs and default docs. + self._load_homepage() + + self.messenger = None + self.connect('shared', self._shared_cb) + + # Get the Presence Service + self.pservice = presenceservice.get_instance() + try: + name, path = self.pservice.get_preferred_connection() + self.tp_conn_name = name + self.tp_conn_path = path + self.conn = telepathy.client.Connection(name, path) + except TypeError: + _logger.debug('Offline') + self.initiating = None + + if self._shared_activity is not None: + _logger.debug('shared: %s' %self._shared_activity.props.joined) + + if self._shared_activity is not None: + # We are joining the activity + _logger.debug('Joined activity') + self.connect('joined', self._joined_cb) + if self.get_shared(): + # We've already joined + self._joined_cb() + else: + _logger.debug('Created activity') + + def _shared_cb(self, activity): + _logger.debug('My activity was shared') + self.initiating = True + self._setup() + + _logger.debug('This is my activity: making a tube...') + id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + SERVICE, {}) + + def _setup(self): + if self._shared_activity is None: + _logger.debug('Failed to share or join activity') + return + + bus_name, conn_path, channel_paths = self._shared_activity.get_channels() + + # Work out what our room is called and whether we have Tubes already + room = None + tubes_chan = None + text_chan = None + for channel_path in channel_paths: + channel = telepathy.client.Channel(bus_name, channel_path) + htype, handle = channel.GetHandle() + if htype == telepathy.HANDLE_TYPE_ROOM: + _logger.debug('Found our room: it has handle#%d "%s"' + %(handle, self.conn.InspectHandles(htype, [handle])[0])) + room = handle + ctype = channel.GetChannelType() + if ctype == telepathy.CHANNEL_TYPE_TUBES: + _logger.debug('Found our Tubes channel at %s'%channel_path) + tubes_chan = channel + elif ctype == telepathy.CHANNEL_TYPE_TEXT: + _logger.debug('Found our Text channel at %s'%channel_path) + text_chan = channel + + if room is None: + _logger.debug("Presence service didn't create a room") + return + if text_chan is None: + _logger.debug("Presence service didn't create a text channel") + return + + # Make sure we have a Tubes channel - PS doesn't yet provide one + if tubes_chan is None: + _logger.debug("Didn't find our Tubes channel, requesting one...") + tubes_chan = self.conn.request_channel(telepathy.CHANNEL_TYPE_TUBES, + telepathy.HANDLE_TYPE_ROOM, room, True) + + self.tubes_chan = tubes_chan + self.text_chan = text_chan + + tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', + self._new_tube_cb) + + def _list_tubes_reply_cb(self, tubes): + for tube_info in tubes: + self._new_tube_cb(*tube_info) + + def _list_tubes_error_cb(self, e): + _logger.debug('ListTubes() failed: %s'%e) + + def _joined_cb(self, activity): + if not self._shared_activity: + return + + _logger.debug('Joined an existing shared activity') + + self.initiating = False + self._setup() + + _logger.debug('This is not my activity: waiting for a tube...') + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( + reply_handler=self._list_tubes_reply_cb, + error_handler=self._list_tubes_error_cb) + + def _new_tube_cb(self, id, initiator, type, service, params, state): + _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' + 'params=%r state=%d' %(id, initiator, type, service, + params, state)) + + if (type == telepathy.TUBE_TYPE_DBUS and + service == SERVICE): + if state == telepathy.TUBE_STATE_LOCAL_PENDING: + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) + + self.tube_conn = TubeConnection(self.conn, + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], + id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) + + _logger.debug('Tube created') + self.messenger = Messenger(self.tube_conn, self.initiating, self.model) + + + def _load_homepage(self): + if os.path.isfile(_LIBRARY_PATH): + self._browser.load_uri('file://' + _LIBRARY_PATH) + else: + self._browser.load_uri('about:blank') + + def _session_history_changed_cb(self, session_history, link): + _logger.debug('NewPage: %s.' %link) + self.current = link + + def _title_changed_cb(self, embed, pspec): + if embed.props.title is not '': + _logger.debug('Title changed=%s' % embed.props.title) + self.webtitle = embed.props.title + + def _get_data_from_file_path(self, file_path): + fd = open(file_path, 'r') + try: + data = fd.read() + finally: + fd.close() + return data + + def read_file(self, file_path): + if self.metadata['mime_type'] == 'text/plain': + data = self._get_data_from_file_path(file_path) + self.model.deserialize(data) + + for link in self.model.data['shared_links']: + _logger.debug('read: url=%s title=%s d=%s' % (link['url'], + link['title'], + link['color'])) + self._add_link_totray(link['url'], + base64.b64decode(link['thumb']), + link['color'], link['title'], + link['owner'], -1, link['hash']) + self._browser.set_session(self.model.data['history']) + elif self.metadata['mime_type'] == 'text/uri-list': + data = self._get_data_from_file_path(file_path) + uris = mime.split_uri_list(data) + if len(uris) == 1: + self._browser.load_uri(uris[0]) + else: + _logger.error('Open uri-list: Does not support' + 'list of multiple uris by now.') + else: + self._browser.load_uri(file_path) + + def write_file(self, file_path): + if not self.metadata['mime_type']: + self.metadata['mime_type'] = 'text/plain' + + if self.metadata['mime_type'] == 'text/plain': + if not self._jobject.metadata['title_set_by_user'] == '1': + if self._browser.props.title: + self.metadata['title'] = self._browser.props.title + + self.model.data['history'] = self._browser.get_session() + + f = open(file_path, 'w') + try: + f.write(self.model.serialize()) + finally: + f.close() + + def _link_add_button_cb(self, button): + _logger.debug('button: Add link: %s.' % self.current) + self._add_link() + + def _key_press_cb(self, widget, event): + if event.state & gtk.gdk.CONTROL_MASK: + if gtk.gdk.keyval_name(event.keyval) == "l": + _logger.debug('keyboard: Add link: %s.' % self.current) + self._add_link() + return True + elif gtk.gdk.keyval_name(event.keyval) == "u": + _logger.debug('keyboard: Show source of the current page') + self._show_source() + return True + elif gtk.gdk.keyval_name(event.keyval) == "minus": + _logger.debug('keyboard: Zoom out') + self._browser.zoom_out() + return True + elif gtk.gdk.keyval_name(event.keyval) == "plus" \ + or gtk.gdk.keyval_name(event.keyval) == "equal" : + _logger.debug('keyboard: Zoom in') + self._browser.zoom_in() + return True + elif gtk.gdk.keyval_name(event.keyval) == "XF86Start": + _logger.debug('keyboard: Show source of the current page SHOW_KEY') + self._show_source() + return True + return False + + def _add_link(self): + ''' take screenshot and add link info to the model ''' + for link in self.model.data['shared_links']: + if link['hash'] == sha.new(self.current).hexdigest(): + _logger.debug('_add_link: link exist already a=%s b=%s' %( + link['hash'], sha.new(self.current).hexdigest())) + return + buffer = self._get_screenshot() + timestamp = time.time() + self.model.add_link(self.current, self.webtitle, buffer, + profile.get_nick_name(), + profile.get_color().to_string(), timestamp) + + if self.messenger is not None: + self.messenger._add_link(self.current, self.webtitle, + profile.get_color().to_string(), + profile.get_nick_name(), + base64.b64encode(buffer), timestamp) + + def _add_link_model_cb(self, model, index): + ''' receive index of new link from the model ''' + link = self.model.data['shared_links'][index] + self._add_link_totray(link['url'], base64.b64decode(link['thumb']), + link['color'], link['title'], + link['owner'], index, link['hash']) + + def _add_link_totray(self, url, buffer, color, title, owner, index, hash): + ''' add a link to the tray ''' + item = LinkButton(url, buffer, color, title, owner, index, hash) + item.connect('clicked', self._link_clicked_cb, url) + item.connect('remove_link', self._link_removed_cb) + self._tray.add_item(item, index) # use index to add to the tray + item.show() + if self._tray.props.visible is False: + self._tray.show() + self.viewtoolbar.traybutton.props.sensitive = True + + def _link_removed_cb(self, button, hash): + ''' remove a link from tray and delete it in the model ''' + self.model.remove_link(hash) + self._tray.remove_item(button) + if len(self._tray.get_children()) == 0: + self.viewtoolbar.traybutton.props.sensitive = False + + def _link_clicked_cb(self, button, url): + ''' an item of the link tray has been clicked ''' + self._browser.load_uri(url) + + def _show_source(self): + self._browser.get_source() + + def _pixbuf_save_cb(self, buf, data): + data[0] += buf + return True + + def get_buffer(self, pixbuf): + data = [""] + pixbuf.save_to_callback(self._pixbuf_save_cb, "png", {}, data) + return str(data[0]) + + def _get_screenshot(self): + window = self._browser.window + width, height = window.get_size() + + screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False, + bits_per_sample=8, width=width, + height=height) + screenshot.get_from_drawable(window, window.get_colormap(), 0, 0, 0, 0, + width, height) + + screenshot = screenshot.scale_simple(style.zoom(100), + style.zoom(80), + gtk.gdk.INTERP_BILINEAR) + + buffer = self.get_buffer(screenshot) + return buffer + + def can_close(self): + if downloadmanager.can_quit(): + return True + else: + alert = Alert() + alert.props.title = _('Download in progress') + alert.props.msg = _('Stopping now will cancel your download') + cancel_icon = Icon(icon_name='dialog-cancel') + alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), cancel_icon) + stop_icon = Icon(icon_name='dialog-ok') + alert.add_button(gtk.RESPONSE_OK, _('Stop'), stop_icon) + stop_icon.show() + self.add_alert(alert) + alert.connect('response', self.__inprogress_response_cb) + alert.show() + self.present() + + def __inprogress_response_cb(self, alert, response_id): + self.remove_alert(alert) + if response_id is gtk.RESPONSE_CANCEL: + logging.debug('Keep on') + elif response_id == gtk.RESPONSE_OK: + logging.debug('Stop downloads and quit') + downloadmanager.remove_all_downloads() + self.close(force=True) + diff --git a/webtoolbar.py b/webtoolbar.py new file mode 100644 index 0000000..7a8bec5 --- /dev/null +++ b/webtoolbar.py @@ -0,0 +1,217 @@ +# Copyright (C) 2006, Red Hat, Inc. +# Copyright (C) 2007, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import logging +from gettext import gettext as _ + +import gobject +import gtk +from xpcom.components import interfaces +from xpcom import components + +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.menuitem import MenuItem +from sugar._sugarext import AddressEntry +import pango + +import sessionhistory +import progresslistener +import filepicker + +_MAX_HISTORY_ENTRIES = 15 + +class WebToolbar(gtk.Toolbar): + __gtype_name__ = 'WebToolbar' + + __gsignals__ = { + 'add-link': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + + def __init__(self, browser): + gtk.Toolbar.__init__(self) + + self._browser = browser + + self._back = ToolButton('go-previous-paired') + self._back.set_tooltip(_('Back')) + self._back.props.sensitive = False + self._back.connect('clicked', self._go_back_cb) + self.insert(self._back, -1) + self._back.show() + + self._forward = ToolButton('go-next-paired') + self._forward.set_tooltip(_('Forward')) + self._forward.props.sensitive = False + self._forward.connect('clicked', self._go_forward_cb) + self.insert(self._forward, -1) + self._forward.show() + + self._stop_and_reload = ToolButton('media-playback-stop') + self._stop_and_reload.connect('clicked', self._stop_and_reload_cb) + self.insert(self._stop_and_reload, -1) + self._stop_and_reload.show() + + self._entry = AddressEntry() + self._entry.connect('activate', self._entry_activate_cb) + + entry_item = gtk.ToolItem() + entry_item.set_expand(True) + entry_item.add(self._entry) + self._entry.show() + + self.insert(entry_item, -1) + entry_item.show() + + self._link_add = ToolButton('emblem-favorite') + self._link_add.set_tooltip(_('Bookmark')) + self._link_add.connect('clicked', self._link_add_clicked_cb) + self.insert(self._link_add, -1) + self._link_add.show() + + progress_listener = progresslistener.get_instance() + progress_listener.connect('location-changed', self._location_changed_cb) + progress_listener.connect('loading-start', self._loading_start_cb) + progress_listener.connect('loading-stop', self._loading_stop_cb) + progress_listener.connect('loading-progress', self._loading_progress_cb) + + session_history = sessionhistory.get_instance() + session_history.connect('session-history-changed', self._session_history_changed_cb) + + self._browser.connect("notify::title", self._title_changed_cb) + + def _session_history_changed_cb(self, session_history, current_page_index): + # We have to wait until the history info is updated. + gobject.idle_add(self._reload_session_history, current_page_index) + + def _location_changed_cb(self, progress_listener, uri): + cls = components.classes['@mozilla.org/intl/texttosuburi;1'] + texttosuburi = cls.getService(interfaces.nsITextToSubURI) + ui_uri = texttosuburi.unEscapeURIForUI(uri.originCharset, uri.spec) + + self._set_address(ui_uri) + self._update_navigation_buttons() + filepicker.cleanup_temp_files() + + def _loading_start_cb(self, progress_listener): + self._set_title(None) + self._set_loading(True) + self._update_navigation_buttons() + + def _loading_stop_cb(self, progress_listener): + self._set_loading(False) + self._update_navigation_buttons() + + def _loading_progress_cb(self, progress_listener, progress): + self._set_progress(progress) + + def _set_progress(self, progress): + self._entry.props.progress = progress + + def _set_address(self, address): + self._entry.props.address = address + + def _set_title(self, title): + self._entry.props.title = title + + def _show_stop_icon(self): + self._stop_and_reload.set_icon('media-playback-stop') + + def _show_reload_icon(self): + self._stop_and_reload.set_icon('view-refresh') + + def _update_navigation_buttons(self): + can_go_back = self._browser.web_navigation.canGoBack + self._back.props.sensitive = can_go_back + + can_go_forward = self._browser.web_navigation.canGoForward + self._forward.props.sensitive = can_go_forward + + def _entry_activate_cb(self, entry): + self._browser.load_uri(entry.props.text) + self._browser.grab_focus() + + def _go_back_cb(self, button): + self._browser.web_navigation.goBack() + + def _go_forward_cb(self, button): + self._browser.web_navigation.goForward() + + def _title_changed_cb(self, embed, spec): + self._set_title(embed.props.title) + + def _stop_and_reload_cb(self, button): + if self._loading: + self._browser.web_navigation.stop(interfaces.nsIWebNavigation.STOP_ALL) + else: + flags = interfaces.nsIWebNavigation.LOAD_FLAGS_NONE + self._browser.web_navigation.reload(flags) + + def _set_loading(self, loading): + self._loading = loading + + if self._loading: + self._show_stop_icon() + self._stop_and_reload.set_tooltip(_('Stop')) + else: + self._show_reload_icon() + self._stop_and_reload.set_tooltip(_('Reload')) + + def _reload_session_history(self, current_page_index=None): + if current_page_index is None: + current_page_index = session_history.index + + for palette in (self._back.get_palette(), self._forward.get_palette()): + for menu_item in palette.menu.get_children(): + palette.menu.remove(menu_item) + + session_history = self._browser.web_navigation.sessionHistory + if current_page_index > _MAX_HISTORY_ENTRIES: + bottom = current_page_index - _MAX_HISTORY_ENTRIES + else: + bottom = 0 + if (session_history.count - current_page_index) > \ + _MAX_HISTORY_ENTRIES: + top = current_page_index + _MAX_HISTORY_ENTRIES + 1 + else: + top = session_history.count + + for i in range(bottom, top): + if i == current_page_index: + continue + + entry = session_history.getEntryAtIndex(i, False) + menu_item = MenuItem(entry.title, text_maxlen=60) + menu_item.connect('activate', self._history_item_activated_cb, i) + + if i < current_page_index: + palette = self._back.get_palette() + palette.menu.prepend(menu_item) + elif i > current_page_index: + palette = self._forward.get_palette() + palette.menu.append(menu_item) + + menu_item.show() + + def _history_item_activated_cb(self, menu_item, index): + self._browser.web_navigation.gotoIndex(index) + + def _link_add_clicked_cb(self, button): + self.emit('add-link') + -- cgit v0.9.1