From cb6bb97d417460c72d9b000bf2bbf05e03cf1296 Mon Sep 17 00:00:00 2001 From: Raul Gutierrez Segales Date: Mon, 31 Jan 2011 17:26:22 +0000 Subject: Add collaboration support from Gnome --- diff --git a/extra/collaborationplugin.py b/extra/collaborationplugin.py new file mode 100644 index 0000000..145e3de --- /dev/null +++ b/extra/collaborationplugin.py @@ -0,0 +1,280 @@ + +import sys +sys.path.append("..") + +import dbus +from gettext import gettext as _ +import gobject +import gtk +from plugin import Plugin +from util.menubuilder import MenuBuilder +from util.configfile import ConfigFile +from util.configwizard import ConfigWizard +import telepathy +from collaboration.neighborhood import get_neighborhood +from collaboration.connectionmanager import get_connection_manager +from collaboration.activity import Activity +from collaboration import telepathyclient +from collaboration.tubeconn import TubeConnection +from TurtleArt.tacollaboration import Collaboration + + +class CollaborationPlugin(Plugin): + + __gsignals__ = { + 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ()), + 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ()), + } + + # Using activity here is kinda misleading cause the + # obj we are receiving is not a Sugar activity. + def __init__(self, activity, config_file_path): + Plugin.__init__(self) + + self._activity = activity + self._neighborhood = None + self._title = "My Turtle Art session" + self._bundle_id = "org.laptop.TurtleArt" + self._activity_id = "1234567" # This could be hashed from the file path (if resuming) + self._nick = "" + self._config_file_path = config_file_path + self._collaboration_config_values = ConfigFile(self._config_file_path) + valid_config_values = { + "nick" : { "type" : "text"}, + "account_id" : { "type" : "text"}, + "password" : { "type" : "text"}, + "server" : { "type" : "text"}, + "port" : { "type" : "integer"}, + "register": { "type" : "boolean"}, + "turtle_color" : { "type" : "text"}, + "colors" : { "type" : "text"} + } + self._collaboration_config_values.set_valid_keys(valid_config_values) + self._collaboration_config_values.connect("configuration-loaded", self._connect_to_neighborhood) + self._collaboration_config_values.connect("configuration-saved", self._connect_to_neighborhood) + self._collaboration_config_values.load() + + def setup(self): + self._collaboration = Collaboration(self.tw, self) + self._collaboration.setup() + + def set_tw(self, turtleart_window): + self.tw = turtleart_window + + def get_menu(self): + menu = gtk.Menu() + + self._activities_submenu = gtk.Menu() + activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, _('Activities')) + menu.append(activities_menu) + + self._buddies_submenu = gtk.Menu() + buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, _('Buddies')) + menu.append(buddies_menu) + + MenuBuilder.make_menu_item(menu, _('Share'), self._share_cb) + MenuBuilder.make_menu_item(menu, _('Configuration'), self._config_neighborhood_cb) + + neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood')) + + return neighborhood_menu + + def get_colors(self): + return self._colors + + def _get_nick(self): + return self._nick + + def _get_activity_id(self): + return self._activity_id + + def _get_bundle_id(self): + return self._bundle_id + + def _get_title(self): + return self._title + + def _get_turtle_color(self): + return self._turtle_color + + def _connect_to_neighborhood(self, config_file_obj): + if self._neighborhood is not None: + return + + print "_connect_to_neighborhood has been called" + + params = {} + params["nickname"] = self._collaboration_config_values.get("nick") + params["account_id"] = self._collaboration_config_values.get("account_id") + params["server"] = self._collaboration_config_values.get("server") + params["port"] = self._collaboration_config_values.get("port") + params["password"] = self._collaboration_config_values.get("password") + params["register"] = self._collaboration_config_values.get("register") + if params["server"] == "": + raise RuntimeError("Invalid server address") + + self._nick = self._collaboration_config_values.get("nick") + self._colors = self._collaboration_config_values.get("colors") + self._turtle_color = self._collaboration_config_values.get("turtle_color") + + self._activities = {} + self._buddies = {} + + print "connecting to the neighborhood" + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + self._client_handler = telepathyclient.get_instance() + self._neighborhood = get_neighborhood(params) + self._neighborhood.connect('activity-added', self._activity_added_cb) + self._neighborhood.connect('activity-removed', self._activity_removed_cb) + self._neighborhood.connect('buddy-added', self._buddy_added_cb) + self._neighborhood.connect('buddy-removed', self._buddy_removed_cb) + + # TODO: + # - show nick of sharer + # - show icon with color of sharer + def _activity_added_cb(self, model, activity_model): + self._activities[activity_model.props.name] = activity_model + self._recreate_available_activities_menu() + + def _activity_removed_cb(self, model, activity_model): + try: + self._activities.pop(activity_model.props.name) + except: + print "Failed to remove activity %s" % activity_model.props.name + + self._recreate_available_activities_menu() + + def _buddy_added_cb(self, activity, buddy): + self._buddies[buddy.get_key()] = buddy + self._recreate_available_buddies_menu() + + def _buddy_removed_cb(self, activity, buddy): + try: + self._buddies.pop(buddy.get_key()) + except: + print "Couldn't remove buddy %s" % buddy.get_key() + self._recreate_available_buddies_menu() + + # TODO: we should have a list of available actions over + # a given buddy. I.e.: a) chat with him b) make friend + # c) invite to current activity + # + def _recreate_available_buddies_menu(self): + for child in self._buddies_submenu.get_children(): + self._buddies_submenu.remove(child) + + for buddy in self._buddies.values(): + key = buddy.get_key() + if key is None: + key = "" + n = buddy.get_nick() + "|" + key[0:15] + MenuBuilder.make_menu_item(self._buddies_submenu, n, self._buddy_actions_cb, buddy) + + def _buddy_actions_cb(self, widget, buddy): + print "do something with %s" % buddy.get_nick() + + # TODO + # - we need an extra menu branch with a) 'Join' button b) List of buddies + def _recreate_available_activities_menu(self): + for child in self._activities_submenu.get_children(): + self._activities_submenu.remove(child) + + for activity in self._activities.values(): + n = activity.props.name + MenuBuilder.make_menu_item(self._activities_submenu, n, self._join_activity_cb, activity) + + def _join_activity_cb(self, widget, activity): + print "Lets try to join..." + + connection_manager = get_connection_manager() + account_path, connection = \ + connection_manager.get_preferred_connection() + if connection is None: + print('No active connection available') + return + + properties = {} + properties["id"] = activity.activity_id + properties["color"] = activity.get_color() + print "room handle according to activity %s" % activity.room_handle + properties["private"] = True + + try: + room_handle = connection.GetActivity(activity.activity_id, + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) + print("room_handle = %s" % str(room_handle)) + self._joined_activity = Activity(account_path, connection, room_handle, + properties=properties) + except: + traceback.print_exc(file=sys.stdout) + + if self._joined_activity.props.joined: + raise RuntimeError('Activity %s is already shared.' % + activity.activity_id) + + _join_id = self._joined_activity.connect('joined', self.__joined_cb) + self._joined_activity.join() + + def __joined_cb(self,activity, success, err): + self.emit('joined') + + def _config_neighborhood_cb(self, widget): + config_w = ConfigWizard(self._config_file_path) + config_items = [ + {"item_label" : _("Nickname"), "item_type" : "text", "item_name" : "nick" }, + { "item_label" : _("Account ID"), "item_type" : "text", "item_name" : "account_id" }, + { "item_label" : _("Server"), "item_type" : "text", "item_name" : "server" }, + { "item_label" : _("Port"), "item_type" : "text", "item_name" : "port" }, + { "item_label" : _("Password"), "item_type" : "text", "item_name" : "password" }, + { "item_label" : _("Register"), "item_type" : "boolean", "item_name" : "register" }, + { "item_label" : _("Colors"), "item_type" : "text", "item_name" : "colors" }, + { "item_label" : _("Turtle Color"), "item_type" : "text", "item_name" : "turtle_color" } + ] + config_w.set_config_items(config_items) + config_w.set_config_file_obj(self._collaboration_config_values) + config_w.show() + + def _share_cb(self, button): + properties = {} + properties['id'] = self._activity._get_activity_id() + properties['type'] = self._activity._get_bundle_id() + properties['name'] = self._activity._get_title() + properties['color'] = self._activity._get_turtle_color() + properties['private'] = False + + connection_manager = get_connection_manager() + account_path, connection = \ + connection_manager.get_preferred_connection() + + if connection is None: + print('No active connection available') + return + + try: + self._activity._shared_activity = Activity(account_path, connection, + properties=properties) + except: + traceback.print_exc(file=sys.stdout) + + if self._activity._shared_activity.props.joined: + raise RuntimeError('Activity %s is already shared.' % + self._activity._get_activity_id()) + + self._activity._shared_activity.share(self.__share_activity_cb, + self.__share_activity_error_cb) + + def __share_activity_cb(self, activity): + """Finish sharing the activity""" + self.emit('shared') + + def __share_activity_error_cb(self, activity, error): + """Notify with GObject event of unsuccessful sharing of activity + """ + print "%s got error: %s" % (activity, error) + +if __name__ == "__main__": + print "testing collaboration" + diff --git a/extra/plugin.py b/extra/plugin.py new file mode 100644 index 0000000..fe95f95 --- /dev/null +++ b/extra/plugin.py @@ -0,0 +1,10 @@ + +import gobject + +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) + + def get_menu(self): + raise RuntimeError("You need to define get_menu for your plugin.") + diff --git a/turtleart.py b/turtleart.py index c32677b..b8ffa08 100755 --- a/turtleart.py +++ b/turtleart.py @@ -50,6 +50,7 @@ from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo from extra.upload import Uploader +from extra.collaborationplugin import CollaborationPlugin from util.menubuilder import MenuBuilder class TurtleMain(): @@ -78,11 +79,22 @@ class TurtleMain(): self._draw_and_quit() else: self._read_initial_pos() + self._init_plugins() self._setup_gtk() self._build_window() - self._uploader.set_tw(self.tw) + self._run_plugins() self._start_gtk() + def _init_plugins(self): + config_file_path = os.path.join(CONFIG_HOME, 'turtleartrc.collab') + self._collab_plugin = CollaborationPlugin(self, config_file_path) + self._uploader = Uploader() + + def _run_plugins(self): + self._uploader.set_tw(self.tw) + self._collab_plugin.set_tw(self.tw) + self._collab_plugin.setup() + def _mkdir_p(path): '''Create a directory in a fashion similar to `mkdir -p`''' try: @@ -135,7 +147,6 @@ class TurtleMain(): # sure our current directory is TA's source dir. os.chdir(os.path.dirname(__file__)) - self._uploader = Uploader() self.ta_file = None self.output_png = False self.i = 0 # FIXME: use a better name for this variable @@ -292,12 +303,15 @@ class TurtleMain(): MenuBuilder.make_menu_item(menu, _('Stop'), self._do_stop_cb) turtle_menu = MenuBuilder.make_sub_menu(menu, _('Turtle')) + collaboration_menu = self._collab_plugin.get_menu() + menu_bar = gtk.MenuBar() menu_bar.append(activity_menu) menu_bar.append(edit_menu) menu_bar.append(view_menu) menu_bar.append(tool_menu) menu_bar.append(turtle_menu) + menu_bar.append(collaboration_menu) return menu_bar def _quit_ta(self, widget=None, e=None): -- cgit v0.9.1