1 /**
2 * Whiteboard session manager
3 * Methods for establishing connections and notifying the user of events
4 *
5 * Authors:
6 * David Yip <yipdw@rose-hulman.edu>
7 *
8 * Copyright (c) 2005 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include "util/ucompose.hpp"
14 #include <glibmm/i18n.h>
15 #include <gtkmm/dialog.h>
16 #include <gtkmm/messagedialog.h>
18 #include "desktop.h"
19 #include "file.h"
20 #include "document.h"
22 #include "prefs-utils.h"
24 #include "xml/repr.h"
26 #include "jabber_whiteboard/defines.h"
27 #include "jabber_whiteboard/typedefs.h"
28 #include "jabber_whiteboard/node-tracker.h"
29 #include "jabber_whiteboard/chat-handler.h"
30 #include "jabber_whiteboard/message-queue.h"
31 #include "jabber_whiteboard/session-manager.h"
32 #include "jabber_whiteboard/invitation-confirm-dialog.h"
34 namespace Inkscape {
36 namespace Whiteboard {
38 void
39 SessionManager::sendRequestToUser(std::string const& recipientJID)
40 {
41 /*
42 Glib::ustring doccopy;
43 if (document != NULL) {
44 doccopy = *document;
45 }
46 */
47 this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 1);
48 this->sendMessage(CONNECT_REQUEST_USER, 0, "", recipientJID.c_str(), false);
49 }
51 void
52 SessionManager::sendRequestToChatroom(Glib::ustring const& server, Glib::ustring const& chatroom, Glib::ustring const& handle, Glib::ustring const& password)
53 {
54 // We do not yet use the Basic MUC Protocol for connection establishment etc
55 // <http://www.jabber.org/jeps/jep-0045.html>. The protocol we use is the
56 // old GroupChat system; extension to MUC is TODO
58 // Compose room@service/nick string for "to" field
59 Glib::ustring dest = String::ucompose("%1@%2/%3", chatroom, server, handle);
60 LmMessage* presence_req = lm_message_new(dest.data(), LM_MESSAGE_TYPE_PRESENCE);
62 // Add 'from' attribute
63 LmMessageNode* preq_root = lm_message_get_node(presence_req);
65 lm_message_node_set_attribute(preq_root, "from", this->session_data->jid.c_str());
67 // Add <x xmlns='http://jabber.org/protocol/muc/' />
68 // (Not anymore: we don't speak it! -- yipdw)
69 LmMessageNode* xmlns_node = lm_message_node_add_child(preq_root, "x", "");
70 lm_message_node_set_attribute(xmlns_node, "xmlns", "http://jabber.org/protocol/muc/");
72 // If a password was supplied, add it to xmlns_node
73 if (password != NULL) {
74 lm_message_node_add_child(xmlns_node, "password", password.c_str());
75 }
77 // Create chat message handler and node tracker
78 if (!this->_myChatHandler) {
79 this->_myChatHandler = new ChatMessageHandler(this);
80 }
82 // Flag ourselves as connecting to a chatroom (but not yet connected)
83 this->session_data->status.set(CONNECTING_TO_CHAT, 1);
84 // Send the message
85 GError *error = NULL;
86 if (!lm_connection_send(this->session_data->connection, presence_req, &error)) {
87 g_error("Presence message could not be sent to %s: %s", dest.data(), error->message);
88 this->session_data->status.set(CONNECTING_TO_CHAT, 0);
89 }
91 this->session_data->chat_handle = handle;
92 this->session_data->chat_server = server;
93 this->session_data->chat_name = chatroom;
95 prefs_set_string_attribute("whiteboard.room", "name", chatroom.c_str());
96 prefs_set_string_attribute("whiteboard.room", "server", server.c_str());
97 //Commented out because you can use whiteboard.server.username
98 //prefs_set_string_attribute("whiteboard.room", "handle", handle.c_str());
99 //store password here?
102 this->setRecipient(String::ucompose("%1@%2", chatroom, server).data());
104 lm_message_unref(presence_req);
105 }
107 void
108 SessionManager::sendConnectRequestResponse(char const* recipientJID, gboolean accepted_request)
109 {
110 if (accepted_request == TRUE) {
111 this->setRecipient(recipientJID);
112 this->session_data->status.set(IN_WHITEBOARD, 1);
113 }
115 this->sendMessage(CONNECT_REQUEST_RESPONSE_USER, accepted_request, "", recipientJID, false);
116 }
118 // When this method is invoked, it means that the user has received an invitation from another peer
119 // to engage in a whiteboard session (i.e. 1:1 communication). The user may accept or reject this invitation.
120 void
121 SessionManager::receiveConnectRequest(gchar const* requesterJID)
122 {
123 int x, y;
124 Gdk::ModifierType mt;
125 Gdk::Display::get_default()->get_pointer(x, y, mt);
127 if (mt & GDK_BUTTON1_MASK) {
128 // Attach a polling timeout
129 this->_notify_incoming_request = Glib::signal_timeout().connect(sigc::bind< 0 >(sigc::mem_fun(*this, &SessionManager::_pollReceiveConnectRequest), requesterJID), 50);
130 return;
131 }
133 if (this->session_data->status[WAITING_FOR_INVITE_RESPONSE]) {
134 // Whoops. Someone tried to invite us while we were inviting someone else
135 // (maybe it was the someone we were trying to invite, maybe it was someone else).
136 //
137 // Our response is to reject the second request, as we can only handle one
138 // invitation at a time. Also, we notify the user (who is waiting for an invitation
139 // response) of the rejection event.
140 this->sendMessage(CONNECT_REQUEST_REFUSED_BY_PEER, 0, "", requesterJID, false);
142 Glib::ustring primary = "<span weight=\"bold\" size=\"larger\">";
144 // TRANSLATORS: This string is used to inform an Inkboard user that the following
145 // scenario has occurred:
146 // 1. Alice invites Bob to an Inkboard session.
147 // 2. While Alice's invitation is en route, Bob invites Alice to an Inkboard session.
148 //
149 // Or, we might have the following scenario:
150 // 1. Alice invites Bob to an Inkboard session.
151 // 2. While Alice is waiting for Bob's response, Carol sends Alice an invitation.
152 //
153 // In the current implementation, we can only handle one invitation at a time,
154 // so we reject all others.
155 //
156 // This is a fix for bug #1352522. Probably not the friendliest, but it's about
157 // the best we can do without changing the protocol.
158 primary += _("<b>An invitation conflict has occurred.</b>");
159 primary += "</span>\n\n";
161 // TRANSLATORS: %1 is the JID of the user who sent us the invitation request.
162 primary += String::ucompose(_("The Jabber user <b>%1</b> attempted to invite you to a whiteboard session while you were waiting on an invitation response.\n\nThe invitation from <b>%1</b> has been rejected."), requesterJID);
164 Gtk::MessageDialog dialog(primary, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_CLOSE, false);
165 dialog.run();
166 return;
167 }
169 if (this->session_data->status[IN_WHITEBOARD]) {
170 this->sendMessage(ALREADY_IN_SESSION, 0, "", requesterJID, false);
171 return;
172 }
174 // Check to see if the user made any modifications to this document. If so,
175 // we want to give them the option of (1) letting us clear their document or (2)
176 // opening a new, blank document for the whiteboard session.
177 Glib::ustring primary = "<span weight=\"bold\" size=\"larger\">" + String::ucompose(_("<b>%1</b> has invited you to a whiteboard session."), requesterJID) + "</span>\n\n";
178 Glib::ustring title = String::ucompose(_("Incoming whiteboard invitation from %1"), requesterJID);
180 if (sp_document_repr_root(this->_myDoc)->attribute("sodipodi:modified") == NULL) {
181 primary += String::ucompose(_("Do you wish to accept <b>%1</b>'s whiteboard session invitation?"), requesterJID);
182 } else {
183 primary += String::ucompose(_("Would you like to accept %1's invitation in a new document window?\nAccepting the invitation in your current window will discard unsaved changes."), requesterJID);
184 }
186 // Construct confirmation dialog
187 InvitationConfirmDialog dialog(primary);
189 dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION);
190 dialog.add_button(_("Decline invitation"), DECLINE_INVITATION);
191 dialog.add_button(_("Accept invitation in new document window"), ACCEPT_INVITATION_IN_NEW_WINDOW);
193 bool undecided = true;
194 InvitationResponses resp = static_cast< InvitationResponses >(dialog.run());
196 while(undecided) {
197 if (resp == ACCEPT_INVITATION) {
198 undecided = false;
199 this->clearDocument();
201 // Create a receive queue for the initiator of this request
202 this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this);
204 this->setupInkscapeInterface();
205 if (dialog.useSessionFile()) {
206 this->session_data->sessionFile = dialog.getSessionFilePath();
207 this->_tryToStartLog();
208 }
209 this->sendConnectRequestResponse(requesterJID, TRUE);
211 } else if (resp == ACCEPT_INVITATION_IN_NEW_WINDOW) {
212 SPDesktop* newdesktop = sp_file_new_default();
213 if (newdesktop != NULL) {
214 undecided = false;
216 // Swap desktops around
218 // Destroy the new desktop's session manager and add this one in
219 delete newdesktop->_whiteboard_session_manager;
220 newdesktop->_whiteboard_session_manager = this;
222 // Assign a new session manager to our old desktop
223 this->_myDesktop->_whiteboard_session_manager = new SessionManager(this->_myDesktop);
225 // Reset our desktop and document pointers
226 this->setDesktop(newdesktop);
228 // Prepare document and send acceptance notification
229 this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this);
230 this->clearDocument();
231 this->setupInkscapeInterface();
232 if (dialog.useSessionFile()) {
233 this->session_data->sessionFile = dialog.getSessionFilePath();
234 this->_tryToStartLog();
235 }
236 this->sendConnectRequestResponse(requesterJID, TRUE);
238 } else {
239 // We could not create a new desktop; ask the user if she or he wants to
240 // replace the current document and accept the invitation, or reject the invitation.
241 // TRANSLATORS: %1 is a userid here
242 Glib::ustring msg = "<span weight=\"bold\" size=\"larger\">" + String::ucompose(_("A new document window could not be opened for a whiteboard session with <b>%1</b>"), requesterJID) + ".</span>\n\nWould you like to accept the whiteboard connection in the active document or refuse the invitation?";
243 InvitationConfirmDialog replace_dialog(msg);
244 dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION);
245 dialog.add_button(_("Decline invitation"), DECLINE_INVITATION);
246 resp = static_cast< InvitationResponses >(dialog.run());
247 }
248 } else {
249 undecided = false;
250 this->sendMessage(CONNECT_REQUEST_REFUSED_BY_PEER, 0, "", requesterJID, false);
251 }
252 }
253 }
255 // When this method is invoked, it means that the other peer
256 // has accepted our request.
257 void
258 SessionManager::receiveConnectRequestResponse(InvitationResponses response, std::string& sender)
259 {
260 this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 0);
262 switch(response) {
263 case ACCEPT_INVITATION:
264 {
266 // Create a receive queue for the other peer.
267 this->session_data->receive_queues[sender] = new ReceiveMessageQueue(this);
269 KeyToNodeMap newids;
270 NodeToKeyMap newnodes;
271 this->_myTracker = new XMLNodeTracker(this);
272 this->setupInkscapeInterface();
273 this->_tryToStartLog();
274 this->resendDocument(this->session_data->recipient, newids, newnodes);
275 this->_myTracker->put(newids, newnodes);
276 // this->_myTracker->dump();
277 this->setupCommitListener();
278 break;
279 }
281 case DECLINE_INVITATION:
282 {
283 // TRANSLATORS: %1 is the peer whom refused our invitation.
284 Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has refused your whiteboard invitation.</span>\n\n"), sender);
286 // TRANSLATORS: %1 is the peer whom refused our invitation, %2 is our Jabber identity.
287 Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as <b>%2</b>, and may send an invitation to <b>%1</b> again, or you may send an invitation to a different user."), sender, this->session_data->jid);
289 Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
290 dialog.run();
291 break;
293 }
295 case PEER_ALREADY_IN_SESSION:
296 {
297 // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session.
298 Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> is already in a whiteboard session.</span>\n\n"), sender);
300 // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session.
301 Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as <b>%1</b>, and may send an invitation to a different user."), sender);
302 Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
303 dialog.run();
305 break;
306 }
307 default:
308 break;
309 }
310 }
312 void
313 SessionManager::receiveConnectRequestResponseChat(gchar const* recipient)
314 {
315 // When responding to connection request responses in chatrooms,
316 // the responding user is already established in the whiteboard session.
317 // Therefore we do not need to perform any setup of observers or dispatchers; the requesting user
318 // will do that.
320 KeyToNodeMap newids;
321 NodeToKeyMap newnodes;
322 this->resendDocument(recipient, newids, newnodes);
323 }
325 bool
326 SessionManager::_pollReceiveConnectRequest(Glib::ustring const recipientJID)
327 {
328 int x, y;
329 Gdk::ModifierType mt;
330 Gdk::Display::get_default()->get_pointer(x, y, mt);
332 if (mt & GDK_BUTTON1_MASK) {
333 return true;
334 } else {
335 this->receiveConnectRequest(recipientJID.c_str());
336 return false;
337 }
338 }
340 }
342 }
344 /*
345 Local Variables:
346 mode:c++
347 c-file-style:"stroustrup"
348 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
349 indent-tabs-mode:nil
350 fill-column:99
351 End:
352 */
353 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :