Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/jarabe/frame/activitiestray.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/jarabe/frame/activitiestray.py')
-rw-r--r--src/jarabe/frame/activitiestray.py424
1 files changed, 417 insertions, 7 deletions
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py
index 8821236..1ff044a 100644
--- a/src/jarabe/frame/activitiestray.py
+++ b/src/jarabe/frame/activitiestray.py
@@ -17,8 +17,11 @@
import logging
from gettext import gettext as _
-import gconf
+import tempfile
+import os
+import gconf
+import dbus
import gtk
from sugar.graphics import style
@@ -31,17 +34,23 @@ from sugar.graphics.palette import Palette, WidgetInvoker
from sugar.graphics.menuitem import MenuItem
from sugar.activity.activityhandle import ActivityHandle
from sugar.activity import activityfactory
+from sugar import mime
from jarabe.model import shell
from jarabe.model import neighborhood
from jarabe.model import owner
from jarabe.model import bundleregistry
+from jarabe.model import filetransfer
from jarabe.view.palettes import JournalPalette, CurrentActivityPalette
from jarabe.view.pulsingicon import PulsingIcon
from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.frame.notification import NotificationIcon
import jarabe.frame
+DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
+DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
+DS_DBUS_PATH = "/org/laptop/sugar/DataStore"
+
class ActivityButton(RadioToolButton):
def __init__(self, home_activity, group):
RadioToolButton.__init__(self, group=group)
@@ -83,12 +92,15 @@ class ActivityButton(RadioToolButton):
self._icon.props.pulsing = False
home_activity.disconnect(self._notify_launching_hid)
-
class BaseInviteButton(ToolButton):
def __init__(self, invite):
ToolButton.__init__(self)
self._invite = invite
+
self._icon = Icon()
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
self.connect('clicked', self.__clicked_cb)
self.connect('destroy', self.__destroy_cb)
self._notif_icon = NotificationIcon()
@@ -127,8 +139,6 @@ class ActivityInviteButton(BaseInviteButton):
self._icon.props.file = activity_model.get_icon_name()
else:
self._icon.props.icon_name = 'image-missing'
- self.set_icon_widget(self._icon)
- self._icon.show()
palette = ActivityInvitePalette(invite)
palette.props.invoker = FrameWidgetInvoker(self)
@@ -177,8 +187,6 @@ class PrivateInviteButton(BaseInviteButton):
self._icon.props.file = self._bundle.get_icon()
else:
self._icon.props.icon_name = 'image-missing'
- self.set_icon_widget(self._icon)
- self._icon.show()
palette = PrivateInvitePalette(invite)
palette.props.invoker = FrameWidgetInvoker(self)
@@ -310,6 +318,8 @@ class ActivitiesTray(HTray):
self._invites.connect('invite-added', self.__invite_added_cb)
self._invites.connect('invite-removed', self.__invite_removed_cb)
+ filetransfer.new_file_transfer.connect(self.__new_file_transfer_cb)
+
def __activity_added_cb(self, home_model, home_activity):
logging.debug('__activity_added_cb: %r' % home_activity)
if self.get_children():
@@ -370,7 +380,7 @@ class ActivitiesTray(HTray):
self._invites.remove_invite(invite)
else:
self._invites.remove_private_invite(invite)
-
+
def __invite_added_cb(self, invites, invite):
self._add_invite(invite)
@@ -398,3 +408,403 @@ class ActivitiesTray(HTray):
self._invite_to_item[invite].destroy()
del self._invite_to_item[invite]
+ def __new_file_transfer_cb(self, **kwargs):
+ file_transfer = kwargs['file_transfer']
+ logging.debug('__new_file_transfer_cb %r' % file_transfer)
+
+ if isinstance(file_transfer, filetransfer.IncomingFileTransfer):
+ button = IncomingTransferButton(file_transfer)
+ elif isinstance(file_transfer, filetransfer.OutgoingFileTransfer):
+ button = OutgoingTransferButton(file_transfer)
+
+ self.add_item(button)
+ button.show()
+
+class BaseTransferButton(ToolButton):
+ """Button with a notification attached
+ """
+ def __init__(self):
+ ToolButton.__init__(self)
+ icon = Icon()
+ self.props.icon_widget = icon
+ icon.show()
+
+ self.notif_icon = NotificationIcon()
+ self.notif_icon.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ def __button_release_event_cb(self, icon, event):
+ if self.notif_icon is not None:
+ frame = jarabe.frame.get_view()
+ frame.remove_notification(self.notif_icon)
+ self.notif_icon = None
+
+class IncomingTransferButton(BaseTransferButton):
+ """UI element representing an ongoing incoming file transfer
+ """
+ def __init__(self, file_transfer):
+ BaseTransferButton.__init__(self)
+
+ self._object_id = None
+ self._metadata = {}
+ self._file_transfer = file_transfer
+ self._file_transfer.connect('notify::state', self.__notify_state_cb)
+ self._file_transfer.connect('notify::transferred-bytes',
+ self.__notify_transferred_bytes_cb)
+
+ icon_name = mime.get_mime_icon(file_transfer.mime_type)
+ icon_theme = gtk.icon_theme_get_default()
+ info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if not info:
+ # display standard icon when icon for mime type is not found
+ icon_name = 'application-octet-stream'
+
+ icon_color = XoColor(file_transfer.buddy.props.color)
+
+ self.props.icon_widget.props.icon_name = icon_name
+ self.props.icon_widget.props.xo_color = icon_color
+
+ self.notif_icon.props.icon_name = icon_name
+ self.notif_icon.props.xo_color = icon_color
+
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self.notif_icon,
+ gtk.CORNER_TOP_LEFT)
+
+ def create_palette(self):
+ palette = IncomingTransferPalette(self._file_transfer)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ return palette
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ if file_transfer.props.state == filetransfer.FT_STATE_OPEN:
+ logging.debug('__notify_state_cb OPEN')
+ self._metadata['title'] = file_transfer.title
+ self._metadata['description'] = file_transfer.description
+ self._metadata['progress'] = '0'
+ self._metadata['keep'] = '0'
+ self._metadata['buddies'] = ''
+ self._metadata['preview'] = ''
+ self._metadata['icon-color'] = file_transfer.buddy.props.color
+ self._metadata['mime_type'] = file_transfer.mime_type
+
+ datastore = self._get_datastore()
+ file_path = ''
+ transfer_ownership = True
+ self._object_id = datastore.create(self._metadata, file_path,
+ transfer_ownership)
+
+ elif file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+ logging.debug('__notify_state_cb COMPLETED')
+ self._metadata['progress'] = '100'
+
+ datastore = self._get_datastore()
+ file_path = file_transfer.destination_path
+ transfer_ownership = True
+ datastore.update(self._object_id, self._metadata, file_path,
+ transfer_ownership,
+ reply_handler=self.__reply_handler_cb,
+ error_handler=self.__error_handler_cb)
+
+ elif file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+ logging.debug('__notify_state_cb CANCELLED')
+ if self._object_id is not None:
+ datastore.delete(self._object_id,
+ reply_handler=self.__reply_handler_cb,
+ error_handler=self.__error_handler_cb)
+ self._object_id = None
+
+ def __notify_transferred_bytes_cb(self, file_transfer, pspec):
+ progress = file_transfer.props.transferred_bytes / \
+ file_transfer.file_size
+ self._metadata['progress'] = str(progress * 100)
+
+ datastore = self._get_datastore()
+ file_path = ''
+ transfer_ownership = True
+ datastore.update(self._object_id, self._metadata, file_path,
+ transfer_ownership,
+ reply_handler=self.__reply_handler_cb,
+ error_handler=self.__error_handler_cb)
+
+ def _get_datastore(self):
+ bus = dbus.SessionBus()
+ remote_object = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH)
+ return dbus.Interface(remote_object, DS_DBUS_INTERFACE)
+
+ def __reply_handler_cb(self):
+ logging.debug('__reply_handler_cb %r' % self._object_id)
+
+ def __error_handler_cb(self, error):
+ logging.debug('__error_handler_cb %r %s' % (self._object_id, error))
+
+class OutgoingTransferButton(BaseTransferButton):
+ """UI element representing an ongoing outgoing file transfer
+ """
+ def __init__(self, file_transfer):
+ BaseTransferButton.__init__(self)
+
+ self._file_transfer = file_transfer
+
+ icon_name = mime.get_mime_icon(file_transfer.mime_type)
+ icon_theme = gtk.icon_theme_get_default()
+ info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if not info:
+ # display standard icon when icon for mime type is not found
+ icon_name = 'application-octet-stream'
+
+ client = gconf.client_get_default()
+ icon_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+
+ self.props.icon_widget.props.icon_name = icon_name
+ self.props.icon_widget.props.xo_color = icon_color
+
+ self.notif_icon.props.icon_name = icon_name
+ self.notif_icon.props.xo_color = icon_color
+
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self.notif_icon,
+ gtk.CORNER_TOP_LEFT)
+
+ def create_palette(self):
+ palette = OutgoingTransferPalette(self._file_transfer)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ return palette
+
+class BaseTransferPalette(Palette):
+ """Base palette class for frame or notification icon for file transfers
+ """
+ def __init__(self, file_transfer):
+ Palette.__init__(self, file_transfer.title)
+
+ self.file_transfer = file_transfer
+
+ self.progress_bar = None
+ self.progress_label = None
+ self._notify_transferred_bytes_handler = None
+
+ self.connect('popup', self.__popup_cb)
+ self.connect('popdown', self.__popdown_cb)
+
+ def __popup_cb(self, palette):
+ self.update_progress()
+ self._notify_transferred_bytes_handler = \
+ self.file_transfer.connect('notify::transferred_bytes',
+ self.__notify_transferred_bytes_cb)
+
+ def __popdown_cb(self, palette):
+ if self._notify_transferred_bytes_handler is not None:
+ self.file_transfer.disconnect(
+ self._notify_transferred_bytes_handler)
+ self._notify_transferred_bytes_handler = None
+
+ def __notify_transferred_bytes_cb(self, file_transfer, pspec):
+ self.update_progress()
+
+ def _format_size(self, size):
+ if size < 1024:
+ return _('%dB') % size
+ elif size < 1048576:
+ return _('%dKB') % (size / 1024)
+ else:
+ return _('%dMB') % (size / 1048576)
+
+ def update_progress(self):
+ logging.debug('update_progress: %r' % self.file_transfer.props.transferred_bytes)
+ if self.progress_bar is None:
+ return
+
+ self.progress_bar.props.fraction = \
+ self.file_transfer.props.transferred_bytes / \
+ float(self.file_transfer.file_size)
+ logging.debug('update_progress: %r' % self.progress_bar.props.fraction)
+
+ transferred = self._format_size(
+ self.file_transfer.props.transferred_bytes)
+ total = self._format_size(self.file_transfer.file_size)
+ self.progress_label.props.label = _('%s of %s') % (transferred, total)
+
+class IncomingTransferPalette(BaseTransferPalette):
+ """Palette for frame or notification icon for incoming file transfers
+ """
+ def __init__(self, file_transfer):
+ BaseTransferPalette.__init__(self, file_transfer)
+
+ self.file_transfer.connect('notify::state', self.__notify_state_cb)
+
+ nick = self.file_transfer.buddy.props.nick
+ self.props.secondary_text = _('Transfer from %r') % nick
+
+ self._update()
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ self._update()
+
+ def _update(self):
+ logging.debug('_update state: %r' % self.file_transfer.props.state)
+ if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
+ menu_item = MenuItem(_('Accept'), icon_name='dialog-ok')
+ menu_item.connect('activate', self.__accept_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Decline'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__decline_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ if self.file_transfer.description:
+ label = gtk.Label(self.file_transfer.description)
+ vbox.add(label)
+ label.show()
+
+ mime_type = self.file_transfer.mime_type
+ type_description = mime.get_mime_description(mime_type)
+
+ size = self._format_size(self.file_transfer.file_size)
+ label = gtk.Label(_('%s (%s)') % (size, type_description))
+ vbox.add(label)
+ label.show()
+
+ elif self.file_transfer.props.state in \
+ [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self.progress_bar = gtk.ProgressBar()
+ vbox.add(self.progress_bar)
+ self.progress_bar.show()
+
+ self.progress_label = gtk.Label('')
+ vbox.add(self.progress_label)
+ self.progress_label.show()
+
+ self.update_progress()
+
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+ # TODO: What to do here?
+ self.update_progress()
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+ # TODO: What to do here?
+ self.update_progress()
+
+ def __accept_activate_cb(self, menu_item):
+ #TODO: figure out the best place to get rid of that temp file
+ extension = mime.get_primary_extension(self.file_transfer.mime_type)
+ fd, file_path = tempfile.mkstemp(suffix=extension,
+ prefix=self._sanitize(self.file_transfer.title))
+ os.close(fd)
+ os.unlink(file_path)
+
+ self.file_transfer.accept(file_path)
+
+ def _sanitize(self, file_name):
+ file_name = file_name.replace('/', '_')
+ file_name = file_name.replace('.', '_')
+ file_name = file_name.replace('?', '_')
+ return file_name
+
+ def __decline_activate_cb(self, menu_item):
+ self.file_transfer.decline()
+
+ def __cancel_activate_cb(self, menu_item):
+ self.file_transfer.cancel()
+
+class OutgoingTransferPalette(Palette):
+ """Palette for frame or notification icon for outgoing file transfers
+ """
+ def __init__(self, file_transfer):
+ BaseTransferPalette.__init__(self, file_transfer)
+
+ self.file_transfer.connect('notify::state', self.__notify_state_cb)
+
+ nick = file_transfer.buddy.props.nick
+ self.props.secondary_text = _('Transfer to %r') % nick
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ self._update()
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ self._update()
+
+ def _update(self):
+ logging.debug('_update state: %r' % self.file_transfer.props.state)
+ if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ if self.file_transfer.description:
+ label = gtk.Label(self.file_transfer.description)
+ vbox.add(label)
+ label.show()
+
+ mime_type = self.file_transfer.mime_type
+ type_description = mime.get_mime_description(mime_type)
+
+ size = self._format_size(self.file_transfer.file_size)
+ label = gtk.Label(_('%s (%s)') % (size, type_description))
+ vbox.add(label)
+ label.show()
+
+ elif self.file_transfer.props.state in \
+ [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self.progress_bar = gtk.ProgressBar()
+ vbox.add(self.progress_bar)
+ self.progress_bar.show()
+
+ self.progress_label = gtk.Label('')
+ vbox.add(self.progress_label)
+ self.progress_label.show()
+
+ self.update_progress()
+
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+ # TODO: What to do here?
+ self.update_progress()
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+ # TODO: What to do here?
+ self.update_progress()
+
+ def __cancel_activate_cb(self, menu_item):
+ self.file_transfer.cancel()
+