diff options
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | extensions/Channel_Interface_Conference.xml | 400 | ||||
-rw-r--r-- | extensions/misc.xml | 1 | ||||
-rw-r--r-- | libempathy-gtk/empathy-contact-menu.c | 9 | ||||
-rw-r--r-- | libempathy-gtk/empathy-contact-selector-dialog.c | 63 | ||||
-rw-r--r-- | libempathy-gtk/empathy-contact-selector-dialog.h | 2 | ||||
-rw-r--r-- | libempathy/empathy-dispatcher.c | 13 | ||||
-rw-r--r-- | libempathy/empathy-tp-chat.c | 109 | ||||
-rw-r--r-- | libempathy/empathy-tp-chat.h | 1 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/empathy-chat-window.c | 99 | ||||
-rw-r--r-- | src/empathy-chat-window.ui | 7 | ||||
-rw-r--r-- | src/empathy-invite-participant-dialog.c | 65 | ||||
-rw-r--r-- | src/empathy-invite-participant-dialog.h | 46 |
14 files changed, 784 insertions, 34 deletions
diff --git a/configure.ac b/configure.ac index 6085e9b35..363e70fb6 100644 --- a/configure.ac +++ b/configure.ac @@ -32,7 +32,7 @@ AC_COPYRIGHT([ GLIB_REQUIRED=2.22.0 GTK_REQUIRED=2.18.0 GCONF_REQUIRED=1.2.0 -TELEPATHY_GLIB_REQUIRED=0.9.0 +TELEPATHY_GLIB_REQUIRED=0.9.2 ENCHANT_REQUIRED=1.2.0 ISO_CODES_REQUIRED=0.35 LIBNOTIFY_REQUIRED=0.4.4 diff --git a/extensions/Channel_Interface_Conference.xml b/extensions/Channel_Interface_Conference.xml new file mode 100644 index 000000000..af3e627b9 --- /dev/null +++ b/extensions/Channel_Interface_Conference.xml @@ -0,0 +1,400 @@ +<?xml version="1.0" ?> +<node name="/Channel_Interface_Conference" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.1 of the License, or (at your option) any later version.</p> + + <p>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.</p> + + <p>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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + <interface + name="org.freedesktop.Telepathy.Channel.Interface.Conference.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Channel"/> + <tp:requires + interface="org.freedesktop.Telepathy.Channel.Interface.Group"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>An interface for multi-user conference channels that can "continue + from" one or more individual channels.</p> + + <tp:rationale> + <p>This interface addresses freedesktop.org <a + href="http://bugs.freedesktop.org/show_bug.cgi?id=24906">bug + #24906</a> (GSM-compatible conference calls) and <a + href="http://bugs.freedesktop.org/show_bug.cgi?id=24939">bug + #24939</a> (upgrading calls and chats to multi-user). + See those bugs for rationale and use cases.</p> + + <p>Examples of usage:</p> + + <p>Active and held GSM calls C1, C2 can be merged into a single + channel Cn with the Conference interface, by calling + <code>CreateChannel({...ChannelType: ...Call, + ...<tp:member-ref>InitialChannels</tp:member-ref>: [C1, C2]})</code> + which returns Cn.</p> + + <p>An XMPP 1-1 conversation C1 can be continued in a newly created + multi-user chatroom Cn by calling + <code>CreateChannel({...ChannelType: ...Text, + ...<tp:member-ref>InitialChannels</tp:member-ref>: [C1]})</code> + which returns Cn.</p> + + <p>An XMPP 1-1 conversation C1 can be continued in a specified + multi-user chatroom by calling + <code>CreateChannel({...ChannelType: ...Text, ...HandleType: ROOM, + ...TargetID: 'telepathy@conf.example.com', + ...<tp:member-ref>InitialChannels</tp:member-ref>: [C1]})</code> + which returns a Conference channel.</p> + + <p>Either of the XMPP cases could work for Call channels, to + upgrade from 1-1 Jingle to multi-user Muji. Any of the XMPP cases + could in principle work for link-local XMPP (XEP-0174).</p> + + <p>The underlying switchboard representing an MSN 1-1 conversation C1 + with a contact X can be moved to a representation as a nameless + chatroom, Cn, to which more contacts can be invited, by calling + <code>CreateChannel({...ChannelType: ...Text, + ...<tp:member-ref>InitialChannels</tp:member-ref>: [C1]})</code> + which returns Cn. C1 SHOULD remain open, with no underlying + switchboard attached. If X establishes a new switchboard with the + local user, C1 SHOULD pick up that switchboard rather than letting + it create a new channel. + <strong>[FIXME: should it?]</strong> + Similarly, if the local user sends a message in C1, then + a new switchboard to X should be created and associated with C1.</p> + + <p>XMPP and MSN do not natively have a concept of merging two or more + channels C1, C2... into one channel, Cn. However, the GSM-style + merging API can be supported on XMPP and MSN, as an API short-cut + for upgrading C1 into a conference Cn (which invites the + TargetHandle of C1 into Cn), then immediately inviting the + TargetHandle of C2, the TargetHandle of C3, etc. into Cn as well.</p> + + <p>With a suitable change of terminology, Skype has behaviour similar + to MSN.</p> + </tp:rationale> + + <p>The <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Interface" + >Group</tp:dbus-ref> MAY have channel-specific handles for participants; + clients SHOULD support both Conferences that have channel-specific handles, + and those that do not.</p> + + <tp:rationale> + <p>In the GSM case, the Conference's Group interface MAY have + channel-specific handles, to reflect the fact that the identities of + the participants might not be known - it can be possible to know that + there is another participant in the Conference, but not know who + they are. + <strong>[FIXME: fact check from GSM gurus needed]</strong> + </p> + + <p>In the XMPP case, the Conference's Group interface SHOULD have + channel-specific handles, to reflect the fact that the participants + have MUC-specific identities, and the user might also be able to see + their global identities, or not.</p> + + <p>In most other cases, including MSN and link-local XMPP, the + Conference's Group interface SHOULD NOT have channel-specific + handles, since users' identities are always visible.</p> + </tp:rationale> + + <p>Connection managers implementing channels with this interface + MUST NOT allow the object paths of channels that could be merged + into a Conference to be re-used, unless the channel re-using the + object path is equivalent to the channel that previously used it.</p> + + <tp:rationale> + <p>If you upgrade some channels into a conference, and then close + the original channels, <tp:member-ref>InitialChannels</tp:member-ref> + (which is immutable) will contain paths to channels which no longer + exist. This implies that you should not re-use channel object paths, + unless future incarnations of the path are equivalent.</p> + + <p>For instance, on protocols where you can only have + zero or one 1-1 text channels with Emily at one time, it would + be OK to re-use the same object path for every 1-1 text channel + with Emily; but on protocols where this is not true, it would + be misleading.</p> + </tp:rationale> + + </tp:docstring> + + <property name="Channels" tp:name-for-bindings="Channels" + access="read" type="ao"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The individual <tp:dbus-ref + namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>s that + are continued by this conference, which have the same <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel" + >ChannelType</tp:dbus-ref> as this one, but with <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel" + >TargetHandleType</tp:dbus-ref> = CONTACT.</p> + + <p>This property MUST NOT be requestable. + <strong>[FIXME: or would it be better for this one, and not IC, to be + requestable?]</strong> + </p> + + <p>Change notification is via the + <tp:member-ref>ChannelMerged</tp:member-ref> and + <tp:member-ref>ChannelRemoved</tp:member-ref> signals.</p> + </tp:docstring> + </property> + + <signal name="ChannelMerged" tp:name-for-bindings="Channel_Merged"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a new channel is added to the value of + <tp:member-ref>Channels</tp:member-ref>.</p> + </tp:docstring> + + <arg name="Channel" type="o"> + <tp:docstring>The channel that was added to + <tp:member-ref>Channels</tp:member-ref>.</tp:docstring> + </arg> + </signal> + + <signal name="ChannelRemoved" tp:name-for-bindings="Channel_Removed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a channel is removed from the value of + <tp:member-ref>Channels</tp:member-ref>, either because it closed + or because it was split using the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Interface" + >Splittable.DRAFT.Split</tp:dbus-ref> method.</p> + + <p><strong>[FIXME: relative ordering of this vs. Closed? Do we + care?]</strong></p> + </tp:docstring> + + <arg name="Channel" type="o"> + <tp:docstring>The channel that was removed from + <tp:member-ref>Channels</tp:member-ref>.</tp:docstring> + </arg> + </signal> + + <property name="InitialChannels" tp:name-for-bindings="Initial_Channels" + access="read" type="ao"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The initial value of <tp:member-ref>Channels</tp:member-ref>.</p> + + <p>This property SHOULD be requestable. Omitting it from a request is + equivalent to providing it with an empty list as value. Requests + where its value has at least two elements SHOULD be expected to + succeed on any implementation of this interface.</p> + + <p>Whether a request with 0 or 1 elements in the list will succeed is + indicated by <tp:member-ref>SupportsNonMerges</tp:member-ref>.</p> + + <tp:rationale> + <p>In GSM, a pair of calls can be merged into a conference. In XMPP + and MSN, you can create a new chatroom, or upgrade one 1-1 channel + into a chatroom; however, on these protocols, it is also possible + to fake GSM-style merging by upgrading the first channel, then + inviting the targets of all the other channels into it.</p> + </tp:rationale> + + <p>If possible, the <tp:member-ref>Channels</tp:member-ref>' states SHOULD + NOT be altered by merging them into a conference. However, depending on + the protocol, the Channels MAY be placed in a "frozen" state by placing + them in this property's value or by calling + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Interface" + >MergeableConference.DRAFT.Merge</tp:dbus-ref> on them. + <strong>[FIXME: there's nothing in RequestableChannelClasses yet + to say what will happen, see #24906 comment 6]</strong></p> + + <tp:rationale> + <p>In Jingle, nothing special will happen to merged calls. UIs MAY + automatically place calls on hold before merging them, if that is + the desired behaviour; this SHOULD always work. Not doing + an implicit hold/unhold seems to preserve least-astonishment.</p> + + <p><strong>[FIXME: check whether ring supports faking Hold on both + channels, as it probably should: see #24906 comment 6]</strong> + </p> + + <p>In GSM, the calls that are merged go into a state similar to + Hold, but they cannot be unheld, only split from the conference + call using <tp:dbus-ref namespace="org.freedesktop.Telepathy" + >Channel.Interface.Splittable.DRAFT.Split</tp:dbus-ref>.</p> + </tp:rationale> + + <p>Depending on the protocol, it might be signalled to remote users + that this channel is a continuation of all the requested channels, + or that it is only a continuation of the first channel in the + list.</p> + + <tp:rationale> + <p>In MSN, the conference steals the underlying switchboard (protocol + construct) from one of its component channels, so the conference + appears to remote users to be a continuation of that channel and no + other. The connection manager has to make some arbitrary choice, so + we arbitrarily mandate that it SHOULD choose the first channel in + the list as the one to continue.</p> + </tp:rationale> + + <p>This property is immutable.</p> + </tp:docstring> + </property> + + <property name="InitialInviteeHandles" + tp:name-for-bindings="Initial_Invitee_Handles" + access="read" type="au" tp:type="Contact_Handle[]"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A list of additional contacts invited to this conference when it + was created.</p> + + <p>This property SHOULD be requestable, and appear in the allowed + properties in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests" + >RequestableChannelClasses</tp:dbus-ref>, in all connection + managers that can implement its semantics (in practice, this is + likely to mean exactly those connection managers where + <tp:member-ref>SupportsNonMerges</tp:member-ref> will be true).</p> + + <p>If included in a request, the given contacts are automatically + invited into the new channel, as if they had been added with + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface" + >Group.AddMembers</tp:dbus-ref>(InitialInviteeHandles, + <tp:member-ref>InvitationMessage</tp:member-ref> immediately after + the channel was created.</p> + + <tp:rationale> + <p>This is a simple convenience API for the common case that a UI + upgrades a 1-1 chat to a multi-user chat solely in order to invite + someone else to participate.</p> + </tp:rationale> + + <p>At most one of InitialInviteeHandles and InitialInviteeIDs may + appear in each request.</p> + + <p>If the local user was not the initiator of this channel, the + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface" + >Group.SelfHandle</tp:dbus-ref> SHOULD appear in the value of this + property, together with any other contacts invited at the same time + (if that information is known).</p> + + <p>This property is immutable.</p> + </tp:docstring> + </property> + + <property name="InitialInviteeIDs" + tp:name-for-bindings="Initial_Invitee_IDs" + access="read" type="as"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A list of additional contacts invited to this conference when it + was created.</p> + + <p>This property SHOULD be requestable, as an alternative to + <tp:member-ref>InitialInviteeHandles</tp:member-ref>. Its semantics + are the same, except that it takes a list of the string + representations of contact handles.</p> + + <p>At most one of InitialInviteeHandles and InitialInviteeIDs may + appear in each request.</p> + + <p>When a channel is created, the values of InitialInviteeHandles and + InitialInviteeIDs MUST correspond to each other.</p> + + <p>This property is immutable.</p> + </tp:docstring> + </property> + + <property name="InvitationMessage" tp:name-for-bindings="Invitation_Message" + access="read" type="s"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The message that was sent to the + <tp:member-ref>InitialInviteeHandles</tp:member-ref> when they were + invited.</p> + + <p>This property SHOULD be requestable, and appear in the allowed + properties in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests" + >RequestableChannelClasses</tp:dbus-ref>, in protocols where + invitations can have an accompanying text message.</p> + + <tp:rationale> + <p>This allows invitations with a message to be sent when using + <tp:member-ref>InitialInviteeHandles</tp:member-ref> or + <tp:member-ref>InitialInviteeIDs</tp:member-ref>.</p> + </tp:rationale> + + <p>If the local user was not the initiator of this channel, the + message with which they were invited (if any) SHOULD appear in the + value of this property.</p> + + <p>This property is immutable.</p> + </tp:docstring> + </property> + + <property name="SupportsNonMerges" + tp:name-for-bindings="Supports_Non_Merges" + access="read" type="b"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p><strong>[FIXME: needs a better name; or perhaps it could be implied + by InitialInviteeHandles being requestable in XMPP/MSN but not in + GSM?]</strong></p> + + <p>If true, requests with <tp:member-ref>InitialChannels</tp:member-ref> + omitted, empty, or one element long should be expected to succeed.</p> + + <p>This property SHOULD appear in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests" + >RequestableChannelClasses</tp:dbus-ref> for + conference channels if and only if its value on those channels will + be true.</p> + + <tp:rationale> + <p>Putting this in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests" + >RequestableChannelClasses</tp:dbus-ref> means clients can find + out whether their request will succeed early enough to do + something about it.</p> + + <p>In XMPP, you can request a channel of type ROOM without + incorporating any 1-1 chats at all - indeed, this is the normal + way to do it - or as a continuation of a single 1-1 chat, and then + invite other people in later.</p> + + <p>The sense of this property is a bit awkward, but it avoids making it + an anti-capability. If the sense were inverted, then its presence in + RequestableChannelClasses would imply that the protocol <em>lacks</em> + a feature; as it stands, it is additive. (Contrast with + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type.StreamedMedia" + >ImmutableStreams</tp:dbus-ref>, which is the wrong way around for + backwards-compatibility reasons.)</p> + </tp:rationale> + + <p>If false, <tp:member-ref>InitialChannels</tp:member-ref> SHOULD be + supplied in all requests for this channel class, and contain at least + two channels. Requests where this requirement is not met SHOULD fail + with NotImplemented. + </p> + + <tp:rationale> + <p>In GSM, you can only make a conference call by merging at least + two channels. + <strong>[FIXME: the CM could conceivably fake it, but that would be + rather nasty]</strong> + </p> + </tp:rationale> + </tp:docstring> + </property> + + </interface> +</node> diff --git a/extensions/misc.xml b/extensions/misc.xml index 746cd9074..6eea49a2d 100644 --- a/extensions/misc.xml +++ b/extensions/misc.xml @@ -7,5 +7,6 @@ <xi:include href="Channel_Handler.xml"/> <xi:include href="Tube_Handler.xml"/> <xi:include href="Debug.xml" /> +<xi:include href="Channel_Interface_Conference.xml" /> </tp:spec> diff --git a/libempathy-gtk/empathy-contact-menu.c b/libempathy-gtk/empathy-contact-menu.c index 4e1adfa90..3d022a8b0 100644 --- a/libempathy-gtk/empathy-contact-menu.c +++ b/libempathy-gtk/empathy-contact-menu.c @@ -483,10 +483,7 @@ static void room_sub_menu_activate_cb (GtkWidget *item, RoomSubMenuData *data) { - TpHandle handle; - GArray handles = {(gchar *) &handle, 1}; EmpathyTpChat *chat; - TpChannel *channel; chat = empathy_chatroom_get_tp_chat (data->chatroom); if (chat == NULL) { @@ -495,10 +492,8 @@ room_sub_menu_activate_cb (GtkWidget *item, } /* send invitation */ - handle = empathy_contact_get_handle (data->contact); - channel = empathy_tp_chat_get_channel (chat); - tp_cli_channel_interface_group_call_add_members (channel, -1, &handles, - _("Inviting to this room"), NULL, NULL, NULL, NULL); + empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat), data->contact, + _("Inviting you to this room")); } static GtkWidget * diff --git a/libempathy-gtk/empathy-contact-selector-dialog.c b/libempathy-gtk/empathy-contact-selector-dialog.c index 58e0fff35..3c59061ae 100644 --- a/libempathy-gtk/empathy-contact-selector-dialog.c +++ b/libempathy-gtk/empathy-contact-selector-dialog.c @@ -50,6 +50,7 @@ typedef struct _EmpathyContactSelectorDialogPriv \ EmpathyContactSelectorDialogPriv; struct _EmpathyContactSelectorDialogPriv { + GtkListStore *store; GtkWidget *account_chooser_label; GtkWidget *account_chooser; GtkWidget *entry_id; @@ -81,13 +82,9 @@ contact_selector_dialog_account_changed_cb (GtkWidget *widget, EmpathyAccountChooser *chooser; TpConnection *connection; GList *members; - GtkListStore *store; - GtkEntryCompletion *completion; /* Remove completions */ - completion = gtk_entry_get_completion (GTK_ENTRY (priv->entry_id)); - store = GTK_LIST_STORE (gtk_entry_completion_get_model (completion)); - gtk_list_store_clear (store); + gtk_list_store_clear (priv->store); /* Get members of the new account */ chooser = EMPATHY_ACCOUNT_CHOOSER (priv->account_chooser); @@ -125,7 +122,7 @@ contact_selector_dialog_account_changed_cb (GtkWidget *widget, empathy_contact_get_name (contact), empathy_contact_get_id (contact)); - gtk_list_store_insert_with_values (store, &iter, -1, + gtk_list_store_insert_with_values (priv->store, &iter, -1, COMPLETION_COL_TEXT, tmpstr, COMPLETION_COL_ID, empathy_contact_get_id (contact), COMPLETION_COL_NAME, empathy_contact_get_name (contact), @@ -241,6 +238,28 @@ account_chooser_filter (TpAccount *account, return class->account_filter (self, account); } +static gboolean +contact_selector_dialog_filter_visible (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + EmpathyContactSelectorDialog *self = EMPATHY_CONTACT_SELECTOR_DIALOG (data); + gboolean r; + char *id; + + gtk_tree_model_get (model, iter, + COMPLETION_COL_ID, &id, + -1); + + /* this must be non-NULL for this function to get called */ + r = EMPATHY_CONTACT_SELECTOR_DIALOG_GET_CLASS (self)->contact_filter ( + self, id); + + g_free (id); + + return r; +} + static void empathy_contact_selector_dialog_init (EmpathyContactSelectorDialog *dialog) { @@ -248,7 +267,6 @@ empathy_contact_selector_dialog_init (EmpathyContactSelectorDialog *dialog) GtkBuilder *gui; gchar *filename; GtkEntryCompletion *completion; - GtkListStore *model; GtkWidget *content_area; GtkWidget *table_contact; @@ -289,19 +307,20 @@ empathy_contact_selector_dialog_init (EmpathyContactSelectorDialog *dialog) gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); /* text completion */ + priv->store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + completion = gtk_entry_completion_new (); - model = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); gtk_entry_completion_set_text_column (completion, COMPLETION_COL_TEXT); gtk_entry_completion_set_match_func (completion, contact_selector_dialog_match_func, NULL, NULL); - gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (model)); + gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (priv->store)); gtk_entry_set_completion (GTK_ENTRY (priv->entry_id), completion); g_signal_connect (completion, "match-selected", G_CALLBACK (contact_selector_dialog_match_selected_cb), dialog); g_object_unref (completion); - g_object_unref (model); + g_object_unref (priv->store); empathy_builder_connect (gui, dialog, "entry_id", "changed", contact_selector_change_state_button_cb, @@ -370,6 +389,27 @@ empathy_contact_selector_dialog_set_property (GObject *self, } static void +empathy_contact_selector_dialog_constructed (GObject *dialog) +{ + EmpathyContactSelectorDialogPriv *priv = GET_PRIV (dialog); + + if (EMPATHY_CONTACT_SELECTOR_DIALOG_GET_CLASS (dialog)->contact_filter) + { + GtkEntryCompletion *completion; + GtkTreeModel *filter; + + completion = gtk_entry_get_completion (GTK_ENTRY (priv->entry_id)); + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL); + + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), + contact_selector_dialog_filter_visible, dialog, NULL); + + gtk_entry_completion_set_model (completion, filter); + g_object_unref (filter); + } +} + +static void empathy_contact_selector_dialog_dispose (GObject *object) { EmpathyContactSelectorDialogPriv *priv = GET_PRIV (object); @@ -392,6 +432,9 @@ empathy_contact_selector_dialog_class_init ( g_type_class_add_private (class, sizeof (EmpathyContactSelectorDialogPriv)); + class->contact_filter = NULL; + + object_class->constructed = empathy_contact_selector_dialog_constructed; object_class->dispose = empathy_contact_selector_dialog_dispose; object_class->get_property = empathy_contact_selector_dialog_get_property; object_class->set_property = empathy_contact_selector_dialog_set_property; diff --git a/libempathy-gtk/empathy-contact-selector-dialog.h b/libempathy-gtk/empathy-contact-selector-dialog.h index d8a698ab5..87b2812f4 100644 --- a/libempathy-gtk/empathy-contact-selector-dialog.h +++ b/libempathy-gtk/empathy-contact-selector-dialog.h @@ -40,6 +40,8 @@ struct _EmpathyContactSelectorDialogClass { gboolean (*account_filter) (EmpathyContactSelectorDialog *self, TpAccount *account); + gboolean (*contact_filter) (EmpathyContactSelectorDialog *self, + const char *id); }; struct _EmpathyContactSelectorDialog { diff --git a/libempathy/empathy-dispatcher.c b/libempathy/empathy-dispatcher.c index 49be0babe..839dababb 100644 --- a/libempathy/empathy-dispatcher.c +++ b/libempathy/empathy-dispatcher.c @@ -1610,6 +1610,19 @@ empathy_dispatcher_call_create_or_ensure_channel ( } } +/** + * empathy_dispatcher_create_channel: + * @self: the EmpathyDispatcher + * @connection: the Connection to dispatch on + * @request: an a{sv} map of properties for the request, i.e. using tp_asv_new() + * @callback: a callback for when the channel arrives (or NULL) + * @user_data: optional user data (or NULL) + * + * When calling this function, #EmpathyDispatcher takes ownership of your + * reference to @request. DO NOT unref or destroy @request. When the request is + * done, @request will be unreferenced. Take another reference if you want to + * keep it around. + */ void empathy_dispatcher_create_channel (EmpathyDispatcher *self, TpConnection *connection, diff --git a/libempathy/empathy-tp-chat.c b/libempathy/empathy-tp-chat.c index e53caefec..381095efc 100644 --- a/libempathy/empathy-tp-chat.c +++ b/libempathy/empathy-tp-chat.c @@ -23,15 +23,15 @@ #include <string.h> -#include <telepathy-glib/channel.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/util.h> -#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/telepathy-glib.h> + +#include <extensions/extensions.h> #include "empathy-tp-chat.h" #include "empathy-tp-contact-factory.h" #include "empathy-contact-monitor.h" #include "empathy-contact-list.h" +#include "empathy-dispatcher.h" #include "empathy-marshal.h" #include "empathy-time.h" #include "empathy-utils.h" @@ -60,6 +60,7 @@ typedef struct { * (channel doesn't implement the Password interface) */ gboolean got_password_flags; gboolean ready; + gboolean can_upgrade_to_muc; } EmpathyTpChatPriv; static void tp_chat_iface_init (EmpathyContactListIface *iface); @@ -117,17 +118,54 @@ tp_chat_add (EmpathyContactList *list, const gchar *message) { EmpathyTpChatPriv *priv = GET_PRIV (list); - TpHandle handle; - GArray handles = {(gchar *) &handle, 1}; - g_return_if_fail (EMPATHY_IS_TP_CHAT (list)); - g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + if (tp_proxy_has_interface_by_id (priv->channel, + TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) { + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; - handle = empathy_contact_get_handle (contact); - tp_cli_channel_interface_group_call_add_members (priv->channel, -1, - &handles, NULL, - NULL, NULL, NULL, - NULL); + g_return_if_fail (EMPATHY_IS_TP_CHAT (list)); + g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + + handle = empathy_contact_get_handle (contact); + tp_cli_channel_interface_group_call_add_members (priv->channel, + -1, &handles, NULL, NULL, NULL, NULL, NULL); + } else if (priv->can_upgrade_to_muc) { + EmpathyDispatcher *dispatcher; + TpConnection *connection; + GHashTable *props; + const char *object_path; + GPtrArray channels = { (gpointer *) &object_path, 1 }; + const char *invitees[2] = { NULL, }; + + dispatcher = empathy_dispatcher_dup_singleton (); + connection = tp_channel_borrow_connection (priv->channel); + + invitees[0] = empathy_contact_get_id (contact); + object_path = tp_proxy_get_object_path (priv->channel); + + props = tp_asv_new ( + TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, + TP_IFACE_CHANNEL_TYPE_TEXT, + TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, + TP_HANDLE_TYPE_NONE, + EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels", + TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels, + EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs", + G_TYPE_STRV, invitees, + /* FIXME: InvitationMessage ? */ + NULL); + + /* Although this is a MUC, it's anonymous, so CreateChannel is + * valid. + * props now belongs to EmpathyDispatcher, don't free it */ + empathy_dispatcher_create_channel (dispatcher, connection, + props, NULL, NULL); + + g_object_unref (dispatcher); + } else { + g_warning ("Cannot add to this channel"); + } } static void @@ -1219,9 +1257,14 @@ tp_chat_constructor (GType type, handles->len, (TpHandle *) handles->data, tp_chat_got_added_contacts_cb, NULL, NULL, chat); + priv->can_upgrade_to_muc = FALSE; + g_signal_connect (priv->channel, "group-members-changed", G_CALLBACK (tp_chat_group_members_changed_cb), chat); } else { + EmpathyDispatcher *dispatcher = empathy_dispatcher_dup_singleton (); + GList *list, *ptr; + /* Get the self contact from the connection's self handle */ handle = tp_connection_get_self_handle (connection); empathy_tp_contact_factory_get_from_handle (priv->factory, @@ -1233,6 +1276,25 @@ tp_chat_constructor (GType type, empathy_tp_contact_factory_get_from_handle (priv->factory, handle, tp_chat_got_remote_contact_cb, NULL, NULL, chat); + + list = empathy_dispatcher_find_requestable_channel_classes ( + dispatcher, connection, + tp_channel_get_channel_type (priv->channel), + TP_HANDLE_TYPE_ROOM, NULL); + + for (ptr = list; ptr; ptr = ptr->next) { + GValueArray *array = ptr->data; + const char **oprops = g_value_get_boxed ( + g_value_array_get_nth (array, 1)); + + if (tp_strv_contains (oprops, EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels")) { + priv->can_upgrade_to_muc = TRUE; + break; + } + } + + g_list_free (list); + g_object_unref (dispatcher); } if (tp_proxy_has_interface_by_id (priv->channel, @@ -1714,3 +1776,24 @@ empathy_tp_chat_provide_password_finish (EmpathyTpChat *self, return TRUE; } + +/** + * empathy_tp_chat_can_add_contact: + * + * Returns: %TRUE if empathy_contact_list_add() will work for this channel. + * That is if this chat is a 1-to-1 channel that can be upgraded to + * a MUC using the Conference interface or if the channel is a MUC. + */ +gboolean +empathy_tp_chat_can_add_contact (EmpathyTpChat *self) +{ + EmpathyTpChatPriv *priv; + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE); + + priv = GET_PRIV (self); + + return priv->can_upgrade_to_muc || + tp_proxy_has_interface_by_id (priv->channel, + TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);; +} diff --git a/libempathy/empathy-tp-chat.h b/libempathy/empathy-tp-chat.h index 940c36095..6b5fc8d0b 100644 --- a/libempathy/empathy-tp-chat.h +++ b/libempathy/empathy-tp-chat.h @@ -96,6 +96,7 @@ void empathy_tp_chat_provide_password_async (EmpathyTpChat *chat, gboolean empathy_tp_chat_provide_password_finish (EmpathyTpChat *chat, GAsyncResult *result, GError **error); +gboolean empathy_tp_chat_can_add_contact (EmpathyTpChat *self); G_END_DECLS diff --git a/src/Makefile.am b/src/Makefile.am index ecec8923f..3370f9bed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -44,6 +44,7 @@ empathy_handwritten_source = \ empathy-import-pidgin.c empathy-import-pidgin.h \ empathy-import-utils.c empathy-import-utils.h \ empathy-import-widget.c empathy-import-widget.h \ + empathy-invite-participant-dialog.c empathy-invite-participant-dialog.h \ empathy-main-window.c empathy-main-window.h \ empathy-new-chatroom-dialog.c empathy-new-chatroom-dialog.h \ empathy-preferences.c empathy-preferences.h \ diff --git a/src/empathy-chat-window.c b/src/empathy-chat-window.c index 658e59426..5ad66fd26 100644 --- a/src/empathy-chat-window.c +++ b/src/empathy-chat-window.c @@ -35,14 +35,14 @@ #include <glib/gi18n.h> #include <libnotify/notification.h> -#include <telepathy-glib/account-manager.h> -#include <telepathy-glib/util.h> +#include <telepathy-glib/telepathy-glib.h> #include <libempathy/empathy-contact.h> #include <libempathy/empathy-message.h> -#include <libempathy/empathy-dispatcher.h> #include <libempathy/empathy-chatroom-manager.h> #include <libempathy/empathy-utils.h> +#include <libempathy/empathy-tp-contact-factory.h> +#include <libempathy/empathy-contact-list.h> #include <libempathy-gtk/empathy-images.h> #include <libempathy-gtk/empathy-conf.h> @@ -56,6 +56,7 @@ #include "empathy-chat-window.h" #include "empathy-about-dialog.h" +#include "empathy-invite-participant-dialog.h" #define DEBUG_FLAG EMPATHY_DEBUG_CHAT #include <libempathy/empathy-debug.h> @@ -346,6 +347,32 @@ chat_window_menu_context_update (EmpathyChatWindowPriv *priv, } static void +chat_window_conversation_menu_update (EmpathyChatWindowPriv *priv, + EmpathyChatWindow *self) +{ + EmpathyTpChat *tp_chat; + TpConnection *connection; + GtkAction *action; + gboolean sensitive = FALSE; + + g_return_if_fail (priv->current_chat != NULL); + + action = gtk_ui_manager_get_action (priv->ui_manager, + "/chats_menubar/menu_conv/menu_conv_invite_participant"); + tp_chat = empathy_chat_get_tp_chat (priv->current_chat); + + if (tp_chat != NULL) { + connection = empathy_tp_chat_get_connection (tp_chat); + + sensitive = empathy_tp_chat_can_add_contact (tp_chat) && + (tp_connection_get_status (connection, NULL) == + TP_CONNECTION_STATUS_CONNECTED); + } + + gtk_action_set_sensitive (action, sensitive); +} + +static void chat_window_contact_menu_update (EmpathyChatWindowPriv *priv, EmpathyChatWindow *window) { @@ -536,6 +563,8 @@ chat_window_update (EmpathyChatWindow *window) chat_window_menu_context_update (priv, num_pages); + chat_window_conversation_menu_update (priv, window); + chat_window_contact_menu_update (priv, window); @@ -819,6 +848,69 @@ chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action, } static void +got_contact_cb (EmpathyTpContactFactory *factory, + EmpathyContact *contact, + const GError *error, + gpointer user_data, + GObject *object) +{ + EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data); + + if (error != NULL) { + DEBUG ("Failed: %s", error->message); + return; + } else { + empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat), + contact, _("Inviting you to this room")); + } +} + +static void +chat_window_invite_participant_activate_cb (GtkAction *action, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + GtkWidget *dialog; + EmpathyTpChat *tp_chat; + TpChannel *channel; + int response; + + priv = GET_PRIV (window); + + g_return_if_fail (priv->current_chat != NULL); + + tp_chat = empathy_chat_get_tp_chat (priv->current_chat); + channel = empathy_tp_chat_get_channel (tp_chat); + + dialog = empathy_invite_participant_dialog_new ( + GTK_WINDOW (priv->dialog)); + gtk_widget_show (dialog); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response == GTK_RESPONSE_ACCEPT) { + TpConnection *connection; + EmpathyTpContactFactory *factory; + const char *id; + + id = empathy_contact_selector_dialog_get_selected ( + EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL); + if (EMP_STR_EMPTY (id)) goto out; + + connection = tp_channel_borrow_connection (channel); + factory = empathy_tp_contact_factory_dup_singleton (connection); + + empathy_tp_contact_factory_get_from_id (factory, id, + got_contact_cb, tp_chat, NULL, NULL); + + g_object_unref (factory); + } + +out: + gtk_widget_destroy (dialog); +} + +static void chat_window_close_activate_cb (GtkAction *action, EmpathyChatWindow *window) { @@ -1725,6 +1817,7 @@ empathy_chat_window_init (EmpathyChatWindow *window) "menu_conv_clear", "activate", chat_window_clear_activate_cb, "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb, "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb, + "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb, "menu_conv_close", "activate", chat_window_close_activate_cb, "menu_edit", "activate", chat_window_edit_activate_cb, "menu_edit_cut", "activate", chat_window_cut_activate_cb, diff --git a/src/empathy-chat-window.ui b/src/empathy-chat-window.ui index 57326a3e3..88d099c16 100644 --- a/src/empathy-chat-window.ui +++ b/src/empathy-chat-window.ui @@ -39,6 +39,12 @@ </object> </child> <child> + <object class="GtkAction" id="menu_conv_invite_participant"> + <property name="name">menu_conv_invite_participant</property> + <property name="label" translatable="yes">Invite _Participant...</property> + </object> + </child> + <child> <object class="GtkAction" id="menu_conv_close"> <property name="stock_id">gtk-close</property> <property name="name">menu_conv_close</property> @@ -145,6 +151,7 @@ <menuitem action="menu_conv_insert_smiley"/> <menuitem action="menu_conv_favorite"/> <menuitem action="menu_conv_toggle_contacts"/> + <menuitem action="menu_conv_invite_participant"/> <separator/> <menuitem action="menu_conv_close"/> </menu> diff --git a/src/empathy-invite-participant-dialog.c b/src/empathy-invite-participant-dialog.c new file mode 100644 index 000000000..70332d1ff --- /dev/null +++ b/src/empathy-invite-participant-dialog.c @@ -0,0 +1,65 @@ +/* + * empathy-invite-participant-dialog.c + * + * EmpathyInviteParticipantDialog + * + * (c) 2009, Collabora Ltd. + * + * Authors: + * Danielle Madeley <danielle.madeley@collabora.co.uk> + */ + +#include <glib/gi18n.h> + +#include "empathy-invite-participant-dialog.h" + +G_DEFINE_TYPE (EmpathyInviteParticipantDialog, + empathy_invite_participant_dialog, EMPATHY_TYPE_CONTACT_SELECTOR_DIALOG); + +static void +empathy_invite_participant_dialog_class_init (EmpathyInviteParticipantDialogClass *klass) +{ +} + +static void +empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self) +{ + EmpathyContactSelectorDialog *parent = EMPATHY_CONTACT_SELECTOR_DIALOG (self); + GtkWidget *label; + char *str; + + label = gtk_label_new (NULL); + str = g_strdup_printf ( + "<span size=\"x-large\" weight=\"bold\">%s</span>\n\n%s", + _("Invite Participant"), + _("Choose a contact to invite into the conversation:")); + gtk_label_set_markup (GTK_LABEL (label), str); + g_free (str); + + gtk_box_pack_start (GTK_BOX (parent->vbox), label, FALSE, TRUE, 0); + /* move to the top -- wish there was a better way to do this */ + gtk_box_reorder_child (GTK_BOX (parent->vbox), label, 0); + gtk_widget_show (label); + + parent->button_action = gtk_dialog_add_button (GTK_DIALOG (self), + "Invite", GTK_RESPONSE_ACCEPT); + gtk_widget_set_sensitive (parent->button_action, FALSE); + + gtk_window_set_title (GTK_WINDOW (self), _("Invite Participant")); + gtk_window_set_role (GTK_WINDOW (self), "invite_participant"); + empathy_contact_selector_dialog_set_show_account_chooser ( + EMPATHY_CONTACT_SELECTOR_DIALOG (self), FALSE); +} + +GtkWidget * +empathy_invite_participant_dialog_new (GtkWindow *parent) +{ + GtkWidget *self = g_object_new (EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, NULL); + + if (parent != NULL) + { + gtk_window_set_transient_for (GTK_WINDOW (self), parent); + } + + return self; +} diff --git a/src/empathy-invite-participant-dialog.h b/src/empathy-invite-participant-dialog.h new file mode 100644 index 000000000..561e10738 --- /dev/null +++ b/src/empathy-invite-participant-dialog.h @@ -0,0 +1,46 @@ +/* + * empathy-invite-participant-dialog.h + * + * EmpathyInviteParticipantDialog + * + * (c) 2009, Collabora Ltd. + * + * Authors: + * Danielle Madeley <danielle.madeley@collabora.co.uk> + */ + +#ifndef __EMPATHY_INVITE_PARTICIPANT_DIALOG_H__ +#define __EMPATHY_INVITE_PARTICIPANT_DIALOG_H__ + +#include <gtk/gtk.h> + +#include <libempathy-gtk/empathy-contact-selector-dialog.h> + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG (empathy_invite_participant_dialog_get_type ()) +#define EMPATHY_INVITE_PARTICIPANT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, EmpathyInviteParticipantDialog)) +#define EMPATHY_INVITE_PARTICIPANT_DIALOG_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, EmpathyInviteParticipantDialogClass)) +#define EMPATHY_IS_INVITE_PARTICIPANT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG)) +#define EMPATHY_IS_INVITE_PARTICIPANT_DIALOG_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG)) +#define EMPATHY_INVITE_PARTICIPANT_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG, EmpathyInviteParticipantDialogClass)) + +typedef struct _EmpathyInviteParticipantDialog EmpathyInviteParticipantDialog; +typedef struct _EmpathyInviteParticipantDialogClass EmpathyInviteParticipantDialogClass; + +struct _EmpathyInviteParticipantDialog +{ + EmpathyContactSelectorDialog parent; +}; + +struct _EmpathyInviteParticipantDialogClass +{ + EmpathyContactSelectorDialogClass parent_class; +}; + +GType empathy_invite_participant_dialog_get_type (void); +GtkWidget *empathy_invite_participant_dialog_new (GtkWindow *parent); + +G_END_DECLS + +#endif |