/** * Whiteboard session manager * Methods for establishing connections and notifying the user of events * * Authors: * David Yip * * Copyright (c) 2005 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "util/ucompose.hpp" #include #include #include #include "desktop.h" #include "file.h" #include "document.h" #include "xml/repr.h" #include "jabber_whiteboard/defines.h" #include "jabber_whiteboard/typedefs.h" #include "jabber_whiteboard/node-tracker.h" #include "jabber_whiteboard/chat-handler.h" #include "jabber_whiteboard/message-queue.h" #include "jabber_whiteboard/session-manager.h" #include "jabber_whiteboard/invitation-confirm-dialog.h" namespace Inkscape { namespace Whiteboard { void SessionManager::sendRequestToUser(std::string const& recipientJID) { /* Glib::ustring doccopy; if (document != NULL) { doccopy = *document; } */ this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 1); this->sendMessage(CONNECT_REQUEST_USER, 0, "", recipientJID.c_str(), false); } void SessionManager::sendRequestToChatroom(Glib::ustring const& server, Glib::ustring const& chatroom, Glib::ustring const& handle, Glib::ustring const& password) { // We do not yet use the Basic MUC Protocol for connection establishment etc // . The protocol we use is the // old GroupChat system; extension to MUC is TODO // Compose room@service/nick string for "to" field Glib::ustring dest = String::ucompose("%1@%2/%3", chatroom, server, handle); LmMessage* presence_req = lm_message_new(dest.data(), LM_MESSAGE_TYPE_PRESENCE); // Add 'from' attribute LmMessageNode* preq_root = lm_message_get_node(presence_req); lm_message_node_set_attribute(preq_root, "from", lm_connection_get_jid(this->session_data->connection)); // Add // (Not anymore: we don't speak it! -- yipdw) LmMessageNode* xmlns_node = lm_message_node_add_child(preq_root, "x", ""); lm_message_node_set_attribute(xmlns_node, "xmlns", "http://jabber.org/protocol/muc/"); // If a password was supplied, add it to xmlns_node if (password != NULL) { lm_message_node_add_child(xmlns_node, "password", password.c_str()); } // Create chat message handler and node tracker if (!this->_myChatHandler) { this->_myChatHandler = new ChatMessageHandler(this); } // Flag ourselves as connecting to a chatroom (but not yet connected) this->session_data->status.set(CONNECTING_TO_CHAT, 1); // Send the message GError *error = NULL; if (!lm_connection_send(this->session_data->connection, presence_req, &error)) { g_error("Presence message could not be sent to %s: %s", dest.data(), error->message); this->session_data->status.set(CONNECTING_TO_CHAT, 0); } this->session_data->chat_handle = handle; this->session_data->chat_server = server; this->session_data->chat_name = chatroom; this->setRecipient(String::ucompose("%1@%2", chatroom, server).data()); lm_message_unref(presence_req); } void SessionManager::sendConnectRequestResponse(char const* recipientJID, gboolean accepted_request) { if (accepted_request == TRUE) { this->setRecipient(recipientJID); this->session_data->status.set(IN_WHITEBOARD, 1); } this->sendMessage(CONNECT_REQUEST_RESPONSE_USER, accepted_request, "", recipientJID, false); } // When this method is invoked, it means that the user has received an invitation from another peer // to engage in a whiteboard session (i.e. 1:1 communication). The user may accept or reject this invitation. void SessionManager::receiveConnectRequest(gchar const* requesterJID) { int x, y; Gdk::ModifierType mt; Gdk::Display::get_default()->get_pointer(x, y, mt); if (mt) { // Attach a polling timeout this->_notify_incoming_request = Glib::signal_timeout().connect(sigc::bind< 0 >(sigc::mem_fun(*this, &SessionManager::_pollReceiveConnectRequest), requesterJID), 50); return; } if (this->session_data->status[WAITING_FOR_INVITE_RESPONSE]) { // Whoops. Someone tried to invite us while we were inviting someone else // (maybe it was the someone we were trying to invite, maybe it was someone else). // // Our response is to reject the second request, as we can only handle one // invitation at a time. Also, we notify the user (who is waiting for an invitation // response) of the rejection event. this->sendMessage(CONNECT_REQUEST_REFUSED_BY_PEER, 0, "", requesterJID, false); Glib::ustring primary = ""; // TRANSLATORS: This string is used to inform an Inkboard user that the following // scenario has occurred: // 1. Alice invites Bob to an Inkboard session. // 2. While Alice's invitation is en route, Bob invites Alice to an Inkboard session. // // Or, we might have the following scenario: // 1. Alice invites Bob to an Inkboard session. // 2. While Alice is waiting for Bob's response, Carol sends Alice an invitation. // // In the current implementation, we can only handle one invitation at a time, // so we reject all others. // // This is a fix for bug #1352522. Probably not the friendliest, but it's about // the best we can do without changing the protocol. primary += _("An invitation conflict has occurred."); primary += "\n\n"; // TRANSLATORS: %1 is the JID of the user who sent us the invitation request. primary += String::ucompose(_("The Jabber user %1 attempted to invite you to a whiteboard session while you were waiting on an invitation response.\n\nThe invitation from %1 has been rejected."), requesterJID); Gtk::MessageDialog dialog(primary, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_CLOSE, false); dialog.run(); return; } if (this->session_data->status[IN_WHITEBOARD]) { this->sendMessage(ALREADY_IN_SESSION, 0, "", requesterJID, false); return; } // Check to see if the user made any modifications to this document. If so, // we want to give them the option of (1) letting us clear their document or (2) // opening a new, blank document for the whiteboard session. Glib::ustring primary = "" + String::ucompose(_("%1 has invited you to a whiteboard session."), requesterJID) + "\n\n"; Glib::ustring title = String::ucompose(_("Incoming whiteboard invitation from %1"), requesterJID); if (sp_document_repr_root(this->_myDoc)->attribute("sodipodi:modified") == NULL) { primary += String::ucompose(_("Do you wish to accept %1's whiteboard session invitation?"), requesterJID); } else { 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); } // Construct confirmation dialog InvitationConfirmDialog dialog(primary); dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION); dialog.add_button(_("Decline invitation"), DECLINE_INVITATION); dialog.add_button(_("Accept invitation in new document window"), ACCEPT_INVITATION_IN_NEW_WINDOW); bool undecided = true; InvitationResponses resp = static_cast< InvitationResponses >(dialog.run()); while(undecided) { if (resp == ACCEPT_INVITATION) { undecided = false; this->clearDocument(); // Create a receive queue for the initiator of this request this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this); this->setupInkscapeInterface(); if (dialog.useSessionFile()) { this->session_data->sessionFile = dialog.getSessionFilePath(); this->_tryToStartLog(); } this->sendConnectRequestResponse(requesterJID, TRUE); } else if (resp == ACCEPT_INVITATION_IN_NEW_WINDOW) { SPDesktop* newdesktop = sp_file_new_default(); if (newdesktop != NULL) { undecided = false; // Swap desktops around // Destroy the new desktop's session manager and add this one in delete newdesktop->_whiteboard_session_manager; newdesktop->_whiteboard_session_manager = this; // Assign a new session manager to our old desktop this->_myDesktop->_whiteboard_session_manager = new SessionManager(this->_myDesktop); // Reset our desktop and document pointers this->setDesktop(newdesktop); // Prepare document and send acceptance notification this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this); this->clearDocument(); this->setupInkscapeInterface(); if (dialog.useSessionFile()) { this->session_data->sessionFile = dialog.getSessionFilePath(); this->_tryToStartLog(); } this->sendConnectRequestResponse(requesterJID, TRUE); } else { // We could not create a new desktop; ask the user if she or he wants to // replace the current document and accept the invitation, or reject the invitation. // TRANSLATORS: %1 is a userid here Glib::ustring msg = "" + String::ucompose(_("A new document window could not be opened for a whiteboard session with %1"), requesterJID) + ".\n\nWould you like to accept the whiteboard connection in the active document or refuse the invitation?"; InvitationConfirmDialog replace_dialog(msg); dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION); dialog.add_button(_("Decline invitation"), DECLINE_INVITATION); resp = static_cast< InvitationResponses >(dialog.run()); } } else { undecided = false; this->sendMessage(CONNECT_REQUEST_REFUSED_BY_PEER, 0, "", requesterJID, false); } } } // When this method is invoked, it means that the other peer // has accepted our request. void SessionManager::receiveConnectRequestResponse(InvitationResponses response, std::string& sender) { this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 0); switch(response) { case ACCEPT_INVITATION: { // Create a receive queue for the other peer. this->session_data->receive_queues[sender] = new ReceiveMessageQueue(this); KeyToNodeMap newids; NodeToKeyMap newnodes; this->_myTracker = new XMLNodeTracker(this); this->setupInkscapeInterface(); this->_tryToStartLog(); this->resendDocument(this->session_data->recipient, newids, newnodes); this->_myTracker->put(newids, newnodes); // this->_myTracker->dump(); this->setupCommitListener(); break; } case DECLINE_INVITATION: { // TRANSLATORS: %1 is the peer whom refused our invitation. Glib::ustring primary = String::ucompose(_("The user %1 has refused your whiteboard invitation.\n\n"), sender); // TRANSLATORS: %1 is the peer whom refused our invitation, %2 is our Jabber identity. Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as %2, and may send an invitation to %1 again, or you may send an invitation to a different user."), sender, lm_connection_get_jid(this->session_data->connection)); Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false); dialog.run(); break; } case PEER_ALREADY_IN_SESSION: { // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session. Glib::ustring primary = String::ucompose(_("The user %1 is already in a whiteboard session.\n\n"), sender); // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session. Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as %1, and may send an invitation to a different user."), sender); Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false); dialog.run(); break; } default: break; } } void SessionManager::receiveConnectRequestResponseChat(gchar const* recipient) { // When responding to connection request responses in chatrooms, // the responding user is already established in the whiteboard session. // Therefore we do not need to perform any setup of observers or dispatchers; the requesting user // will do that. KeyToNodeMap newids; NodeToKeyMap newnodes; this->resendDocument(recipient, newids, newnodes); } bool SessionManager::_pollReceiveConnectRequest(Glib::ustring const recipientJID) { int x, y; Gdk::ModifierType mt; Gdk::Display::get_default()->get_pointer(x, y, mt); if (mt) { return true; } else { this->receiveConnectRequest(recipientJID.c_str()); return false; } } } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :