Code

removed lm_initialize calls and supporting infrastructure; loudmouth 1.1.1 does not...
[inkscape.git] / src / jabber_whiteboard / session-manager.cpp
1 /**
2  * Whiteboard session manager
3  *
4  * Authors:
5  * David Yip <yipdw@rose-hulman.edu>
6  *
7  * Copyright (c) 2005 Authors
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
12 /*
13 #include "inkscape.h"
14 */
16 #include <cstring>
18 #include <glibmm/i18n.h>
19 #include <gtkmm/dialog.h>
20 #include <gtkmm/messagedialog.h>
21 #include <gtkmm/filechooserdialog.h>
22 #include <gtkmm/stock.h>
24 #include "gc-anchored.h"
26 #include "xml/repr.h"
27 #include "xml/node-observer.h"
29 #include "util/ucompose.hpp"
31 #include "message-context.h"
32 #include "message-stack.h"
33 #include "desktop-handles.h"
34 #include "document.h"
35 #include "document-private.h"
36 #include "verbs.h"
38 #include "jabber_whiteboard/defines.h"
39 #include "jabber_whiteboard/typedefs.h"
40 #include "jabber_whiteboard/deserializer.h"
41 #include "jabber_whiteboard/message-utilities.h"
42 #include "jabber_whiteboard/message-handler.h"
43 #include "jabber_whiteboard/node-tracker.h"
44 #include "jabber_whiteboard/jabber-handlers.h"
45 #include "jabber_whiteboard/callbacks.h"
46 #include "jabber_whiteboard/chat-handler.h"
47 #include "jabber_whiteboard/session-file.h"
48 #include "jabber_whiteboard/session-file-player.h"
49 #include "jabber_whiteboard/session-manager.h"
50 #include "jabber_whiteboard/message-aggregator.h"
51 #include "jabber_whiteboard/undo-stack-observer.h"
52 #include "jabber_whiteboard/serializer.h"
54 //#include "jabber_whiteboard/pedro/pedroxmpp.h"
56 #include "jabber_whiteboard/message-node.h"
57 #include "jabber_whiteboard/message-queue.h"
59 namespace Inkscape {
61 namespace Whiteboard {
63 SessionData::SessionData(SessionManager *sm)
64 {
65         this->_sm = sm;
66         this->recipient = NULL;
67         this->connection = NULL;
68         this->ssl = NULL;
69         this->ignoreFurtherSSLErrors = false;
70         this->send_queue = new SendMessageQueue(sm);
71         this->sequence_number = 1;
72 }
74 SessionData::~SessionData()
75 {
76         this->receive_queues.clear();
78         if (this->send_queue) {
79                 delete this->send_queue;
80         }
81 }
83 SessionManager::SessionManager(::SPDesktop *desktop) 
84 {
86         // Initialize private members to NULL to facilitate deletion in destructor
87         this->_myDoc = NULL;
88         this->session_data = NULL;
89         this->_myCallbacks = NULL;
90         this->_myTracker = NULL;
91         this->_myChatHandler = NULL;
92         this->_mySessionFile = NULL;
93         this->_mySessionPlayer = NULL;
94         this->_myMessageHandler = NULL;
95         this->_myUndoObserver = NULL;
96         this->_mySerializer = NULL;
97         this->_myDeserializer = NULL;
99         this->setDesktop(desktop);
101         if (this->_myDoc == NULL) {
102                 g_error("Initializing SessionManager on null document object!");
103         }
106 #ifdef WIN32
107     //# lm_initialize() must be called before any network code
108 /*
109     if (!lm_initialize_called) {
110         lm_initialize();
111         lm_initialize_called = true;
112         }
113 */
114 #endif
116         this->_setVerbSensitivity(INITIAL);
119 SessionManager::~SessionManager()
122         if (this->session_data) {
123                 if (this->session_data->status[IN_WHITEBOARD]) {
124                         // also calls closeSession
125                         this->disconnectFromDocument();
126                 }
127                 this->disconnectFromServer();
129                 if (this->session_data->status[LOGGED_IN]) {
130                         // TODO: unref message handlers
131                 }
132         }
134         if (this->_mySessionFile) {
135                 delete this->_mySessionPlayer;
136                 delete this->_mySessionFile;
137         }
139         delete this->_myChatHandler;
142         // Deletion of _myTracker is done in closeSession;
143         // no need to do it here.
145         // Deletion is handled separately from session teardown and server disconnection
146         // because some teardown methods (e.g. closeSession) require access to members that we will
147         // be deleting. Separating deletion from teardown means that we do not have
148         // to worry (as much) about proper ordering of the teardown sequence.  (We still need
149         // to ensure that destructors in each object being deleted have access to all the
150         // members they need, though.)
152         // Stop dispatchers
153         if (this->_myCallbacks) {
154                 this->stopSendQueueDispatch();
155                 this->stopReceiveQueueDispatch();
156                 delete this->_myCallbacks;
157         }
159         delete this->_myMessageHandler;
161         delete this->session_data;
163         Inkscape::GC::release(this->_myDoc);
167 void
168 SessionManager::setDesktop(::SPDesktop* desktop)
170         this->_myDesktop = desktop;
171         if (this->_myDoc != NULL) {
172                 Inkscape::GC::release(this->_myDoc);
173         }
174         if (sp_desktop_document(desktop) != NULL) {
175                 this->_myDoc = sp_desktop_document(desktop);
176                 Inkscape::GC::anchor(this->_myDoc);
177         }
180 int
181 SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
183         GError* error = NULL;
184         Glib::ustring jid;
186         // JID format is username@server/resource
187         jid += username + "@" + server + "/" + RESOURCE_NAME;
189         LmMessage* m;
190         LmMessageHandler* mh;
192         if (!this->session_data) {
193                 this->session_data = new SessionData(this);
194         }
196         if (!this->_myMessageHandler) {
197                 this->_myMessageHandler = new MessageHandler(this);
198         }
200         // Connect to server
201         // We need to check to see if this object already exists, because
202         // the user may be reusing an old connection that failed due to e.g.
203         // authentication failure.
204         if (this->session_data->connection) {
205                 lm_connection_close(this->session_data->connection, &error);
206                 lm_connection_unref(this->session_data->connection);
207         }
209         this->session_data->connection = lm_connection_new(server.c_str());
211         lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
213         g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened connection to %s at port %s.  Connecting...",
214                     server.c_str(), port.c_str());
216         if (usessl) {
217                 if (lm_ssl_is_supported()) {
218                         this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
220                         lm_ssl_ref(this->session_data->ssl);
221                 } else {
222                         return SSL_INITIALIZATION_ERROR;
223                 }
224                 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
225         }
227         // Send authorization
228         lm_connection_set_jid(this->session_data->connection, jid.c_str());
230         //      TODO:
231         //      Asynchronous connection and authentication would be nice,
232         //      but it's a huge mess of mixing C callbacks and C++ method calls.
233         //      I've tried to do it and only managed to severely destabilize the Jabber
234         //      server connection routines.
235         //
236         //      This, of course, is an invitation to anyone more capable than me
237         //      to convert this from synchronous to asynchronous Loudmouth calls.
238         if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
239                 if (error != NULL) {
240                         std::cout << "Failed to open: " <<  error->message << std::endl;
241                 }
242                 return FAILED_TO_CONNECT;
243         }
245         g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened Loudmouth connection in blocking mode.");
247         // Authenticate
248         if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
249                 if (error != NULL) {
250                         g_warning("Failed to authenticate: %s", error->message);
251                 }
252                 lm_connection_close(this->session_data->connection, NULL);
253                 lm_connection_unref(this->session_data->connection);
254                 this->session_data->connection = NULL;
255                 return INVALID_AUTH;
256         }
258         g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
260         // Register message handler for presence messages
261         mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
262         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
264         // Register message handler for stream error messages
265         mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
266         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
268         // Register message handler for chat messages
269         mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
270         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
272         // Send presence message to server
273         m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
274         if (!lm_connection_send(this->session_data->connection, m, &error)) {
275                 if (error != NULL) {
276                         g_warning("Presence message could not be sent: %s", error->message);
277                 }
278                 lm_connection_close(this->session_data->connection, NULL);
279                 lm_connection_unref(this->session_data->connection);
280                 this->session_data->connection = NULL;
281                 return FAILED_TO_CONNECT;
282         }
284         this->session_data->status.set(LOGGED_IN, 1);
286         this->_myCallbacks = new Callbacks(this);
288         lm_message_unref(m);
290         this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
292         return CONNECT_SUCCESS;
295 LmSSLResponse
296 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
298         if (this->session_data->ignoreFurtherSSLErrors) {
299                 return LM_SSL_RESPONSE_CONTINUE;
300         }
302         Glib::ustring msg;
304         // TODO: It'd be nice to provide the user with additional information in some cases,
305         // like fingerprints, hostname, etc.
306         switch(status) {
307                 case LM_SSL_STATUS_NO_CERT_FOUND:
308                         msg = _("No SSL certificate was found.");
309                         break;
310                 case LM_SSL_STATUS_UNTRUSTED_CERT:
311                         msg = _("The SSL certificate provided by the Jabber server is untrusted.");
312                         break;
313                 case LM_SSL_STATUS_CERT_EXPIRED:
314                         msg = _("The SSL certificate provided by the Jabber server is expired.");
315                         break;
316                 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
317                         msg = _("The SSL certificate provided by the Jabber server has not been activated.");
318                         break;
319                 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
320                         msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
321                         break;
322                 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
323                         msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
324                         break;
325                 case LM_SSL_STATUS_GENERIC_ERROR:
326                         msg = _("An unknown error occurred while setting up the SSL connection.");
327                         break;
328         }
330         // TRANSLATORS: %1 is the message that describes the specific error that occurred when
331         // establishing the SSL connection.
332         Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
334         Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
335         dlg.add_button(_("Continue connecting and ignore further errors"), 0);
336         dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
337         dlg.add_button(_("Cancel connection"), 2);
339         switch(dlg.run()) {
340                 case 0:
341                         this->session_data->ignoreFurtherSSLErrors = true;
342                         /* FALL-THROUGH */
343                 case 1:
344                         return LM_SSL_RESPONSE_CONTINUE;
346                 default:
347                         return LM_SSL_RESPONSE_STOP;
348         }
351 void
352 SessionManager::disconnectFromServer()
354         if (this->session_data->connection) {
355                 GError* error = NULL;
357                 LmMessage *m;
358                 this->disconnectFromDocument();
359                 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
360                 if (!lm_connection_send(this->session_data->connection, m, &error)) {
361                         g_warning("Could not send unavailable presence message: %s", error->message);
362                 }
364                 lm_message_unref(m);
365                 lm_connection_close(this->session_data->connection, NULL);
366                 lm_connection_unref(this->session_data->connection);
367                 if (this->session_data->ssl) {
368                         lm_ssl_unref(this->session_data->ssl);
369                 }
371                 this->session_data->connection = NULL;
372                 this->session_data->ssl = NULL;
373                 this->_setVerbSensitivity(INITIAL);
374         }
377 void
378 SessionManager::disconnectFromDocument()
380         if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
381                 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
382         }
383         this->closeSession();
384         this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
387 void
388 SessionManager::closeSession()
391         if (this->session_data->status[IN_WHITEBOARD]) {
392                 this->session_data->status.set(IN_WHITEBOARD, 0);
393                 this->session_data->receive_queues.clear();
394                 this->session_data->send_queue->clear();
395                 this->stopSendQueueDispatch();
396                 this->stopReceiveQueueDispatch();
397         }
399         if (this->_myUndoObserver) {
400                 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
401         }
403         delete this->_myUndoObserver;
404         delete this->_mySerializer;
405         delete this->_myDeserializer;
407         this->_myUndoObserver = NULL;
408         this->_mySerializer = NULL;
409         this->_myDeserializer = NULL;
411         if (this->_myTracker) {
412                 delete this->_myTracker;
413                 this->_myTracker = NULL;
414         }
417         this->setRecipient(NULL);
420 void
421 SessionManager::setRecipient(char const* recipientJID)
423         if (this->session_data->recipient) {
424                 free(const_cast< gchar* >(this->session_data->recipient));
425         }
427         if (recipientJID == NULL) {
428                 this->session_data->recipient = NULL;
429         } else {
430                 this->session_data->recipient = g_strdup(recipientJID);
431         }
434 void
435 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
437         if (!this->session_data->status[IN_WHITEBOARD]) {
438                 return;
439         }
441         std::string& recipient = const_cast< std::string& >(recipientJID);
442         if (recipient.empty()) {
443                 recipient = this->session_data->recipient;
444         }
445                 
447         switch (type) {
448                 case DOCUMENT_BEGIN:
449                 case DOCUMENT_END:
450                 case CHANGE_NOT_REPEATABLE:
451                 case CHANGE_REPEATABLE:
452                 case CHANGE_COMMIT:
453                 {
454                         MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
455                         this->session_data->send_queue->insert(newNode);
456                         Inkscape::GC::release(newNode);
457                         break;
458                 }
459                 default:
460                         g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message.  This may lead to desynchronization!");
461                         break;
462         }
466 // FIXME:
467 // This method needs a massive, massive, massive overhaul.
468 int
469 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
471         LmMessage* m;
472         GError* error = NULL;
473         char* type, * seq;
475         if (recipientJID == NULL || recipientJID == "") {
476                 g_warning("Null recipient JID specified; not sending message.");
477                 return NO_RECIPIENT_JID;
478         } else {
479         }
481         // create message
482         m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
484         // add sender
485         lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
487         // set message subtype according to whether or not this is
488         // destined for a chatroom
489         if (chatroom) {
490                 lm_message_node_set_attribute(m->node, "type", "groupchat");
491         } else {
492                 lm_message_node_set_attribute(m->node, "type", "chat");
493         }
495         // set protocol version;
496         // we are currently fixed at version 1
497         lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
499         // add message type
500         type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
501         snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
502         lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
503         free(type);
505         // add message body
506         if (!msg.empty()) {
507                 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
508         } else {
509         }
511         // add sequence number
512         switch(msgtype) {
513                 case CHANGE_REPEATABLE:
514                 case CHANGE_NOT_REPEATABLE:
515                 case DUMMY_CHANGE:
516                 case CHANGE_COMMIT:
517                 case DOCUMENT_BEGIN:
518                 case DOCUMENT_END:
519                 case CONNECT_REQUEST_RESPONSE_CHAT:
520                 case CONNECT_REQUEST_RESPONSE_USER:
521                 case CHATROOM_SYNCHRONIZE_RESPONSE:
522                         seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
523                         sprintf(seq, "%u", sequence);
524                         lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
525                         free(seq);
526                         break;
528                 case CONNECT_REQUEST_USER:
529                 case CONNECTED_SIGNAL:
530                 case DISCONNECTED_FROM_USER_SIGNAL:
531                         break;
533                 // Error messages and synchronization requests do not need a sequence number
534                 case CHATROOM_SYNCHRONIZE_REQUEST:
535                 case CONNECT_REQUEST_REFUSED_BY_PEER:
536                 case UNSUPPORTED_PROTOCOL_VERSION:
537                 case ALREADY_IN_SESSION:
538                         break;
540                 default:
541                         g_warning("Outgoing message type not recognized; not sending.");
542                         lm_message_unref(m);
543                         return UNKNOWN_OUTGOING_TYPE;
544         }
546         // We want to log messages even if they were not successfully sent,
547         // since the user may opt to re-synchronize a session using the session
548         // file record.
549         if (!msg.empty()) {
550                 this->_log(msg);
551                 this->_commitLog();
552         }
554         // send message
556         if (!lm_connection_send(this->session_data->connection, m, &error)) {
557                 g_warning("Send failed: %s", error->message);
558                 lm_message_unref(m);
559                 return CONNECTION_ERROR;
560         }
562         lm_message_unref(m);
563         return SEND_SUCCESS;
566 void
567 SessionManager::connectionError(Glib::ustring const& errmsg)
569         Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
570         dlg.run();
571 //      sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
574 void
575 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
577         Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
578         this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
580         Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
582         if(root == NULL) {
583                 return;
584     }
586         NewChildObjectMessageList newchildren;
587         MessageAggregator& agg = MessageAggregator::instance();
588         Glib::ustring buf;
590     for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
591                 // TODO: replace with Serializer methods
592                 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
594                 NewChildObjectMessageList::iterator j = newchildren.begin();
595                 Glib::ustring aggbuf;
597                 for(; j != newchildren.end(); j++) {
598                         if (!agg.addOne(*j, aggbuf)) {
599                                 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
600                                 aggbuf.clear();
601                                 agg.addOne(*j, aggbuf);
602                         }
603                 }
605                 // send remaining changes
606                 if (!aggbuf.empty()) {
607                         this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
608                         aggbuf.clear();
609                 }
611                 newchildren.clear();
612                 buf.clear();
613     }
615         Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
616         this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
617         Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
618         this->sendChange(docend, DOCUMENT_END, recipientJID, false);
621 void
622 SessionManager::receiveChange(Glib::ustring const& changemsg)
625         struct Node part;
627         Glib::ustring msgcopy = changemsg.c_str();
630         while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
631                 // TODO:
632                 // Yikes.  This is ugly.
633                 if (part.tag == MESSAGE_CHANGE) {
634                         this->_myDeserializer->deserializeEventChgAttr(part.data);
635                         msgcopy.erase(0, part.next_pos);
637                 } else if (part.tag == MESSAGE_NEWOBJ) {
638                         this->_myDeserializer->deserializeEventAdd(part.data);
639                         msgcopy.erase(0, part.next_pos);
641                 } else if (part.tag == MESSAGE_DELETE) {
642                         this->_myDeserializer->deserializeEventDel(part.data);
643                         msgcopy.erase(0, part.next_pos);
645                 } else if (part.tag == MESSAGE_DOCUMENT) {
646                         // no special handler, just keep going with the rest of the message
647                         msgcopy.erase(0, part.next_pos);
649                 } else if (part.tag == MESSAGE_NODECONTENT) {
650                         this->_myDeserializer->deserializeEventChgContent(part.data);
651                         msgcopy.erase(0, part.next_pos);
653                 } else if (part.tag == MESSAGE_ORDERCHANGE) {
654                         this->_myDeserializer->deserializeEventChgOrder(part.data);
655                         msgcopy.erase(0, part.next_pos);
657                 } else if (part.tag == MESSAGE_COMMIT) {
658                         // Retrieve the deserialized event log, node actions, and nodes with updated attributes
659                         XML::Event* log = this->_myDeserializer->getEventLog();
660                         KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
661                         AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
663                         // Make document insensitive to undo
664                         gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
665                         sp_document_set_undo_sensitive(this->_myDoc, FALSE);
667                         // Replay the log and push it onto the undo stack
668                         sp_repr_replay_log(log);
670                         // Call updateRepr on changed nodes
671                         // This is required for some tools to function properly, i.e. text tool
672                         // (TODO: we don't need to update _all_ changed nodes, just their parents)
673                         AttributesUpdatedSet::iterator i = updated.begin();
674                         for(; i != updated.end(); i++) {
675                                 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
676                                 if (updated) {
677                                         updated->updateRepr();
678                                 }
679                         }
681                         // merge the events generated by updateRepr
682                         sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
683                         this->_myDoc->priv->partial = NULL;
685                         this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
687                         // Restore undo sensitivity
688                         sp_document_set_undo_sensitive(this->_myDoc, saved);
690                         // Add or delete nodes to/from the tracker
691                         this->_myTracker->process(node_changes);
693                         // Reset deserializer state
694                         this->_myDeserializer->reset();
695                         break;
697                 } else if (part.tag == MESSAGE_UNDO) {
698                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
699                         sp_document_undo(this->_myDoc);
700                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
701                         msgcopy.erase(0, part.next_pos);
703                 } else if (part.tag == MESSAGE_REDO) {
704                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
705                         sp_document_redo(this->_myDoc);
706                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
707                         msgcopy.erase(0, part.next_pos);
709                 } else if (part.tag == MESSAGE_DOCBEGIN) {
710                         msgcopy.erase(0, part.next_pos);
712                 } else if (part.tag == MESSAGE_DOCEND) {
713                         // Set this to be the new original state of the document
714                         sp_document_done(this->document());
715                         sp_document_clear_redo(this->document());
716                         sp_document_clear_undo(this->document());
717                         this->setupCommitListener();
718                         msgcopy.erase(0, part.next_pos);
720                 } else {
721                         msgcopy.erase(0, part.next_pos);
723                 }
724         }
726         this->_log(changemsg);
728         this->_commitLog();
731 bool
732 SessionManager::isPlayingSessionFile()
734         return this->session_data->status[PLAYING_SESSION_FILE];
737 void
738 SessionManager::loadSessionFile(Glib::ustring filename)
740         if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
741                 try {
742                         if (this->_mySessionFile) {
743                                 delete this->_mySessionFile;
744                         }
745                         this->_mySessionFile = new SessionFile(filename, true, false);
747                         // Initialize objects needed for session playback
748                         if (this->_mySessionPlayer == NULL) {
749                                 this->_mySessionPlayer = new SessionFilePlayer(16, this);
750                         } else {
751                                 this->_mySessionPlayer->load(this->_mySessionFile);
752                         }
754                         if (this->_myTracker == NULL) {
755                                 this->_myTracker = new XMLNodeTracker(this);
756                         } else {
757                                 this->_myTracker->reset();
758                         }
760                         if (!this->session_data) {
761                                 this->session_data = new SessionData(this);
762                         }
764                         if (!this->_myDeserializer) {
765                                 this->_myDeserializer = new Deserializer(this->node_tracker());
766                         }
768                         if (!this->_myUndoObserver) {
769                                 this->setupCommitListener();
770                         }
772                         this->session_data->status.set(PLAYING_SESSION_FILE, 1);
775                 } catch (Glib::FileError e) {
776                         g_warning("Could not load session file: %s", e.what().data());
777                 }
778         }
781 void
782 SessionManager::userConnectedToWhiteboard(gchar const* JID)
784         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
788 void
789 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
792         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
794         // Inform the user
795         // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
796         // This message is not used in a chatroom context.
797         Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
798         // TRANSLATORS: %1 and %2 are userids
799         Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as <b>%2</b>, and may establish a new session to <b>%1</b> or a different user."), JID, lm_connection_get_jid(this->session_data->connection));
801         // TODO: parent this dialog to the active desktop
802         Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
803         /*
804         dialog.set_message(primary, true);
805         dialog.set_secondary_text(secondary, true);
806         */
807         dialog.run();
810 void
811 SessionManager::startSendQueueDispatch()
813         this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
816 void
817 SessionManager::stopSendQueueDispatch()
819         if (this->_send_queue_dispatcher) {
820                 this->_send_queue_dispatcher.disconnect();
821         }
824 void
825 SessionManager::startReceiveQueueDispatch()
827         this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
830 void
831 SessionManager::stopReceiveQueueDispatch()
833         if (this->_receive_queue_dispatcher) {
834                 this->_receive_queue_dispatcher.disconnect();
835         }
838 void
839 SessionManager::clearDocument()
841         // clear all layers, definitions, and metadata
842         XML::Node* rroot = this->_myDoc->rroot;
844         // clear definitions
845         XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
846         g_assert(SP_ROOT(this->_myDoc->root)->defs);
848         for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
849                 defs->removeChild(child);
850         }
852         // clear layers
853         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
854                 if (strcmp(child->name(), "svg:g") == 0) {
855                         rroot->removeChild(child);
856                 }
857         }
859         // clear metadata
860         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
861                 if (strcmp(child->name(), "svg:metadata") == 0) {
862                         rroot->removeChild(child);
863                 }
864         }
866 //      sp_document_done(this->_myDoc);
869 void
870 SessionManager::setupInkscapeInterface()
872         this->session_data->status.set(IN_WHITEBOARD, 1);
873         this->startSendQueueDispatch();
874         this->startReceiveQueueDispatch();
875         if (!this->_myTracker) {
876                 this->_myTracker = new XMLNodeTracker(this);
877         }
879         this->_mySerializer = new Serializer(this->node_tracker());
880         this->_myDeserializer = new Deserializer(this->node_tracker());
882         // We're in a whiteboard session now, so set verb sensitivity accordingly
883         this->_setVerbSensitivity(ESTABLISHED_SESSION);
886 void
887 SessionManager::setupCommitListener()
889         this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
890         this->_myDoc->addUndoObserver(*this->_myUndoObserver);
893 ::SPDesktop*
894 SessionManager::desktop()
896         return this->_myDesktop;
899 ::SPDocument*
900 SessionManager::document()
902         return this->_myDoc;
905 Callbacks*
906 SessionManager::callbacks()
908         return this->_myCallbacks;
911 Whiteboard::UndoStackObserver*
912 SessionManager::undo_stack_observer()
914         return this->_myUndoObserver;
917 Serializer*
918 SessionManager::serializer()
920         return this->_mySerializer;
923 XMLNodeTracker*
924 SessionManager::node_tracker()
926         return this->_myTracker;
930 ChatMessageHandler*
931 SessionManager::chat_handler()
933         return this->_myChatHandler;
936 SessionFilePlayer*
937 SessionManager::session_player()
939         return this->_mySessionPlayer;
942 SessionFile*
943 SessionManager::session_file()
945         return this->_mySessionFile;
948 void
949 SessionManager::_log(Glib::ustring const& message)
951         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
952                 this->_mySessionFile->addMessage(message);
953         }
956 void
957 SessionManager::_commitLog()
959         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
960                 this->_mySessionFile->commit();
961         }
964 void
965 SessionManager::_closeLog()
967         if (this->_mySessionFile) {
968                 this->_mySessionFile->close();
969         }
972 void
973 SessionManager::startLog(Glib::ustring filename)
975         try {
976                 this->_mySessionFile = new SessionFile(filename, false, false);
977         } catch (Glib::FileError e) {
978                 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
979                 throw;
980         }
983 void
984 SessionManager::_tryToStartLog()
986         if (this->session_data) {
987                 if (!this->session_data->sessionFile.empty()) {
988                         bool undecided = true;
989                         while(undecided) {
990                                 try {
991                                         this->startLog(this->session_data->sessionFile);
992                                         undecided = false;
993                                 } catch (Glib::FileError e) {
994                                         undecided = true;
995                                         Glib::ustring msg = String::ucompose(_("Could not open file %1 for session recording.\nThe error encountered was: %2.\n\nYou may select a different location to record the session, or you may opt to not record this session."), this->session_data->sessionFile, e.what());
996                                         Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
997                                         dlg.add_button(_("Choose a different location"), 0);
998                                         dlg.add_button(_("Skip session recording"), 1);
999                                         switch (dlg.run()) {
1000                                                 case 0:
1001                                                 {
1002                                                         Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1003                                                         sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1004                                                         sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1005                                                         int result = sessionfiledlg.run();
1006                                                         switch (result) {
1007                                                                 case Gtk::RESPONSE_OK:
1008                                                                 {
1009                                                                         this->session_data->sessionFile = sessionfiledlg.get_filename();
1010                                                                         break;
1011                                                                 }
1012                                                                 case Gtk::RESPONSE_CANCEL:
1013                                                                 default:
1014                                                                         undecided = false;
1015                                                                         break;
1016                                                         }
1017                                                         break;
1018                                                 }
1019                                                 case 1:
1020                                                 default:
1021                                                         undecided = false;
1022                                                         break;
1023                                         }
1024                                 }
1025                         }
1026                 }
1027         }
1030 void
1031 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1033         return;
1035         switch (mode) {
1036                 case ESTABLISHED_CONNECTION:
1037                         // Upon successful connection, we can disconnect from the server.
1038                         // We can also start sharing a document with a user or chatroom.
1039                         // We cannot, however, connect to a new server without first disconnecting.
1040                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1041                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1042                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1043                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1044                         break;
1046                 case ESTABLISHED_SESSION:
1047                         // When we have established a session, we should not permit the user to go and
1048                         // establish another session from the same document without first disconnecting.
1049                         //
1050                         // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1051                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1052                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1053                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1054                         break;
1055                 case DISCONNECTED_FROM_SESSION:
1056                         // Upon disconnecting from a session, we can establish a new session and disconnect
1057                         // from the server, but we cannot disconnect from a session (since we just left it.)
1058                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1059                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1060                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1062                 case INITIAL:
1063                 default:
1064                         // Upon construction, there is no active connection, so we cannot do the following:
1065                         // (1) disconnect from a session
1066                         // (2) disconnect from a server
1067                         // (3) share with a user 
1068                         // (4) share with a chatroom
1069                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1070                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1071                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1072                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1073                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1074                         break;
1075         };
1078 /*
1079 void
1080 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1082         int type = event.getType();
1084         switch (type) {
1085                 case Pedro::XmppEvent::EVENT_STATUS:
1086                         break;
1087                 case Pedro::XmppEvent::EVENT_ERROR:
1088                         break;
1089                 case Pedro::XmppEvent::EVENT_CONNECTED:
1090                         break;
1091                 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1092                         break;
1093                 case Pedro::XmppEvent::EVENT_MESSAGE:
1094                         break;
1095                 case Pedro::XmppEvent::EVENT_PRESENCE:
1096                         break;
1097                 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1098                         break;
1099                 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1100                         break;
1101                 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1102                         break;
1103                 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1104                         break;
1105         }
1107 */
1114 /*
1115   Local Variables:
1116   mode:c++
1117   c-file-style:"stroustrup"
1118   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1119   indent-tabs-mode:nil
1120   fill-column:99
1121   End:
1122 */
1123 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :