Code

refactor redrawing code into pen_redraw_all; cancel current unfinished path, inctead...
[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 #ifdef WIN32
64 static bool lm_initialize_called = false;
65 #endif
67 SessionData::SessionData(SessionManager *sm)
68 {
69         this->_sm = sm;
70         this->recipient = NULL;
71         this->connection = NULL;
72         this->ssl = NULL;
73         this->ignoreFurtherSSLErrors = false;
74         this->send_queue = new SendMessageQueue(sm);
75         this->sequence_number = 1;
76 }
78 SessionData::~SessionData()
79 {
80         this->receive_queues.clear();
82         if (this->send_queue) {
83                 delete this->send_queue;
84         }
85 }
87 SessionManager::SessionManager(::SPDesktop *desktop) 
88 {
90         // Initialize private members to NULL to facilitate deletion in destructor
91         this->_myDoc = NULL;
92         this->session_data = NULL;
93         this->_myCallbacks = NULL;
94         this->_myTracker = NULL;
95         this->_myChatHandler = NULL;
96         this->_mySessionFile = NULL;
97         this->_mySessionPlayer = NULL;
98         this->_myMessageHandler = NULL;
99         this->_myUndoObserver = NULL;
100         this->_mySerializer = NULL;
101         this->_myDeserializer = NULL;
103         this->setDesktop(desktop);
105         if (this->_myDoc == NULL) {
106                 g_error("Initializing SessionManager on null document object!");
107         }
110 #ifdef WIN32
111     //# lm_initialize() must be called before any network code
112     if (!lm_initialize_called) {
113         lm_initialize();
114         lm_initialize_called = true;
115         }
116 #endif
118         this->_setVerbSensitivity(INITIAL);
121 SessionManager::~SessionManager()
124         if (this->session_data) {
125                 if (this->session_data->status[IN_WHITEBOARD]) {
126                         // also calls closeSession
127                         this->disconnectFromDocument();
128                 }
129                 this->disconnectFromServer();
131                 if (this->session_data->status[LOGGED_IN]) {
132                         // TODO: unref message handlers
133                 }
134         }
136         if (this->_mySessionFile) {
137                 delete this->_mySessionPlayer;
138                 delete this->_mySessionFile;
139         }
141         delete this->_myChatHandler;
144         // Deletion of _myTracker is done in closeSession;
145         // no need to do it here.
147         // Deletion is handled separately from session teardown and server disconnection
148         // because some teardown methods (e.g. closeSession) require access to members that we will
149         // be deleting. Separating deletion from teardown means that we do not have
150         // to worry (as much) about proper ordering of the teardown sequence.  (We still need
151         // to ensure that destructors in each object being deleted have access to all the
152         // members they need, though.)
154         // Stop dispatchers
155         if (this->_myCallbacks) {
156                 this->stopSendQueueDispatch();
157                 this->stopReceiveQueueDispatch();
158                 delete this->_myCallbacks;
159         }
161         delete this->_myMessageHandler;
163         delete this->session_data;
165         Inkscape::GC::release(this->_myDoc);
169 void
170 SessionManager::setDesktop(::SPDesktop* desktop)
172         this->_myDesktop = desktop;
173         if (this->_myDoc != NULL) {
174                 Inkscape::GC::release(this->_myDoc);
175         }
176         if (sp_desktop_document(desktop) != NULL) {
177                 this->_myDoc = sp_desktop_document(desktop);
178                 Inkscape::GC::anchor(this->_myDoc);
179         }
182 int
183 SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
185         GError* error = NULL;
186         Glib::ustring jid;
188         // JID format is username@server/resource
189         jid += username + "@" + server + "/" + RESOURCE_NAME;
191         LmMessage* m;
192         LmMessageHandler* mh;
194         if (!this->session_data) {
195                 this->session_data = new SessionData(this);
196         }
198         if (!this->_myMessageHandler) {
199                 this->_myMessageHandler = new MessageHandler(this);
200         }
202         // Connect to server
203         // We need to check to see if this object already exists, because
204         // the user may be reusing an old connection that failed due to e.g.
205         // authentication failure.
206         if (this->session_data->connection) {
207                 lm_connection_close(this->session_data->connection, &error);
208                 lm_connection_unref(this->session_data->connection);
209         }
211         this->session_data->connection = lm_connection_new(server.c_str());
213         lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
215         if (usessl) {
216                 if (lm_ssl_is_supported()) {
217                         this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
219                         lm_ssl_ref(this->session_data->ssl);
220                 } else {
221                         return SSL_INITIALIZATION_ERROR;
222                 }
223                 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
224         }
226         // Send authorization
227         lm_connection_set_jid(this->session_data->connection, jid.c_str());
229         //      TODO:
230         //      Asynchronous connection and authentication would be nice,
231         //      but it's a huge mess of mixing C callbacks and C++ method calls.
232         //      I've tried to do it and only managed to severely destabilize the Jabber
233         //      server connection routines.
234         //
235         //      This, of course, is an invitation to anyone more capable than me
236         //      to convert this from synchronous to asynchronous Loudmouth calls.
237         if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
238                 if (error != NULL) {
239                         g_warning("Failed to open: %s", error->message);
240                 }
241                 return FAILED_TO_CONNECT;
242         }
244         // Authenticate
245         if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
246                 if (error != NULL) {
247                         g_warning("Failed to authenticate: %s", error->message);
248                 }
249                 lm_connection_close(this->session_data->connection, NULL);
250                 lm_connection_unref(this->session_data->connection);
251                 this->session_data->connection = NULL;
252                 return INVALID_AUTH;
253         }
255         // Register message handler for presence messages
256         mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
257         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
259         // Register message handler for stream error messages
260         mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
261         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
263         // Register message handler for chat messages
264         mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
265         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
267         // Send presence message to server
268         m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
269         if (!lm_connection_send(this->session_data->connection, m, &error)) {
270                 if (error != NULL) {
271                         g_warning("Presence message could not be sent: %s", error->message);
272                 }
273                 lm_connection_close(this->session_data->connection, NULL);
274                 lm_connection_unref(this->session_data->connection);
275                 this->session_data->connection = NULL;
276                 return FAILED_TO_CONNECT;
277         }
279         this->session_data->status.set(LOGGED_IN, 1);
281         this->_myCallbacks = new Callbacks(this);
283         lm_message_unref(m);
285         this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
287         return CONNECT_SUCCESS;
290 LmSSLResponse
291 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
293         if (this->session_data->ignoreFurtherSSLErrors) {
294                 return LM_SSL_RESPONSE_CONTINUE;
295         }
297         Glib::ustring msg;
299         // TODO: It'd be nice to provide the user with additional information in some cases,
300         // like fingerprints, hostname, etc.
301         switch(status) {
302                 case LM_SSL_STATUS_NO_CERT_FOUND:
303                         msg = _("No SSL certificate was found.");
304                         break;
305                 case LM_SSL_STATUS_UNTRUSTED_CERT:
306                         msg = _("The SSL certificate provided by the Jabber server is untrusted.");
307                         break;
308                 case LM_SSL_STATUS_CERT_EXPIRED:
309                         msg = _("The SSL certificate provided by the Jabber server is expired.");
310                         break;
311                 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
312                         msg = _("The SSL certificate provided by the Jabber server has not been activated.");
313                         break;
314                 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
315                         msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
316                         break;
317                 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
318                         msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
319                         break;
320                 case LM_SSL_STATUS_GENERIC_ERROR:
321                         msg = _("An unknown error occurred while setting up the SSL connection.");
322                         break;
323         }
325         // TRANSLATORS: %1 is the message that describes the specific error that occurred when
326         // establishing the SSL connection.
327         Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
329         Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
330         dlg.add_button(_("Continue connecting and ignore further errors"), 0);
331         dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
332         dlg.add_button(_("Cancel connection"), 2);
334         switch(dlg.run()) {
335                 case 0:
336                         this->session_data->ignoreFurtherSSLErrors = true;
337                         /* FALL-THROUGH */
338                 case 1:
339                         return LM_SSL_RESPONSE_CONTINUE;
341                 default:
342                         return LM_SSL_RESPONSE_STOP;
343         }
346 void
347 SessionManager::disconnectFromServer()
349         if (this->session_data->connection) {
350                 GError* error = NULL;
352                 LmMessage *m;
353                 this->disconnectFromDocument();
354                 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
355                 if (!lm_connection_send(this->session_data->connection, m, &error)) {
356                         g_warning("Could not send unavailable presence message: %s", error->message);
357                 }
359                 lm_message_unref(m);
360                 lm_connection_close(this->session_data->connection, NULL);
361                 lm_connection_unref(this->session_data->connection);
362                 if (this->session_data->ssl) {
363                         lm_ssl_unref(this->session_data->ssl);
364                 }
366                 this->session_data->connection = NULL;
367                 this->session_data->ssl = NULL;
368                 this->_setVerbSensitivity(INITIAL);
369         }
372 void
373 SessionManager::disconnectFromDocument()
375         if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
376                 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
377         }
378         this->closeSession();
379         this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
382 void
383 SessionManager::closeSession()
386         if (this->session_data->status[IN_WHITEBOARD]) {
387                 this->session_data->status.set(IN_WHITEBOARD, 0);
388                 this->session_data->receive_queues.clear();
389                 this->session_data->send_queue->clear();
390                 this->stopSendQueueDispatch();
391                 this->stopReceiveQueueDispatch();
392         }
394         if (this->_myUndoObserver) {
395                 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
396         }
398         delete this->_myUndoObserver;
399         delete this->_mySerializer;
400         delete this->_myDeserializer;
402         this->_myUndoObserver = NULL;
403         this->_mySerializer = NULL;
404         this->_myDeserializer = NULL;
406         if (this->_myTracker) {
407                 delete this->_myTracker;
408                 this->_myTracker = NULL;
409         }
412         this->setRecipient(NULL);
415 void
416 SessionManager::setRecipient(char const* recipientJID)
418         if (this->session_data->recipient) {
419                 free(const_cast< gchar* >(this->session_data->recipient));
420         }
422         if (recipientJID == NULL) {
423                 this->session_data->recipient = NULL;
424         } else {
425                 this->session_data->recipient = g_strdup(recipientJID);
426         }
429 void
430 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
432         if (!this->session_data->status[IN_WHITEBOARD]) {
433                 return;
434         }
436         std::string& recipient = const_cast< std::string& >(recipientJID);
437         if (recipient.empty()) {
438                 recipient = this->session_data->recipient;
439         }
440                 
442         switch (type) {
443                 case DOCUMENT_BEGIN:
444                 case DOCUMENT_END:
445                 case CHANGE_NOT_REPEATABLE:
446                 case CHANGE_REPEATABLE:
447                 case CHANGE_COMMIT:
448                 {
449                         MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
450                         this->session_data->send_queue->insert(newNode);
451                         Inkscape::GC::release(newNode);
452                         break;
453                 }
454                 default:
455                         g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message.  This may lead to desynchronization!");
456                         break;
457         }
461 // FIXME:
462 // This method needs a massive, massive, massive overhaul.
463 int
464 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
466         LmMessage* m;
467         GError* error = NULL;
468         char* type, * seq;
470         if (recipientJID == NULL || recipientJID == "") {
471                 g_warning("Null recipient JID specified; not sending message.");
472                 return NO_RECIPIENT_JID;
473         } else {
474         }
476         // create message
477         m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
479         // add sender
480         lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
482         // set message subtype according to whether or not this is
483         // destined for a chatroom
484         if (chatroom) {
485                 lm_message_node_set_attribute(m->node, "type", "groupchat");
486         } else {
487                 lm_message_node_set_attribute(m->node, "type", "chat");
488         }
490         // set protocol version;
491         // we are currently fixed at version 1
492         lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
494         // add message type
495         type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
496         snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
497         lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
498         free(type);
500         // add message body
501         if (!msg.empty()) {
502                 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
503         } else {
504         }
506         // add sequence number
507         switch(msgtype) {
508                 case CHANGE_REPEATABLE:
509                 case CHANGE_NOT_REPEATABLE:
510                 case DUMMY_CHANGE:
511                 case CHANGE_COMMIT:
512                 case DOCUMENT_BEGIN:
513                 case DOCUMENT_END:
514                 case CONNECT_REQUEST_RESPONSE_CHAT:
515                 case CONNECT_REQUEST_RESPONSE_USER:
516                 case CHATROOM_SYNCHRONIZE_RESPONSE:
517                         seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
518                         sprintf(seq, "%u", sequence);
519                         lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
520                         free(seq);
521                         break;
523                 case CONNECT_REQUEST_USER:
524                 case CONNECTED_SIGNAL:
525                 case DISCONNECTED_FROM_USER_SIGNAL:
526                         break;
528                 // Error messages and synchronization requests do not need a sequence number
529                 case CHATROOM_SYNCHRONIZE_REQUEST:
530                 case CONNECT_REQUEST_REFUSED_BY_PEER:
531                 case UNSUPPORTED_PROTOCOL_VERSION:
532                 case ALREADY_IN_SESSION:
533                         break;
535                 default:
536                         g_warning("Outgoing message type not recognized; not sending.");
537                         lm_message_unref(m);
538                         return UNKNOWN_OUTGOING_TYPE;
539         }
541         // We want to log messages even if they were not successfully sent,
542         // since the user may opt to re-synchronize a session using the session
543         // file record.
544         if (!msg.empty()) {
545                 this->_log(msg);
546                 this->_commitLog();
547         }
549         // send message
551         if (!lm_connection_send(this->session_data->connection, m, &error)) {
552                 g_warning("Send failed: %s", error->message);
553                 lm_message_unref(m);
554                 return CONNECTION_ERROR;
555         }
557         lm_message_unref(m);
558         return SEND_SUCCESS;
561 void
562 SessionManager::connectionError(Glib::ustring const& errmsg)
564         Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
565         dlg.run();
566 //      sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
569 void
570 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
572         Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
573         this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
575         Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
577         if(root == NULL) {
578                 return;
579     }
581         NewChildObjectMessageList newchildren;
582         MessageAggregator& agg = MessageAggregator::instance();
583         Glib::ustring buf;
585     for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
586                 // TODO: replace with Serializer methods
587                 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
589                 NewChildObjectMessageList::iterator j = newchildren.begin();
590                 Glib::ustring aggbuf;
592                 for(; j != newchildren.end(); j++) {
593                         if (!agg.addOne(*j, aggbuf)) {
594                                 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
595                                 aggbuf.clear();
596                                 agg.addOne(*j, aggbuf);
597                         }
598                 }
600                 // send remaining changes
601                 if (!aggbuf.empty()) {
602                         this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
603                         aggbuf.clear();
604                 }
606                 newchildren.clear();
607                 buf.clear();
608     }
610         Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
611         this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
612         Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
613         this->sendChange(docend, DOCUMENT_END, recipientJID, false);
616 void
617 SessionManager::receiveChange(Glib::ustring const& changemsg)
620         struct Node part;
622         Glib::ustring msgcopy = changemsg.c_str();
625         while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
626                 // TODO:
627                 // Yikes.  This is ugly.
628                 if (part.tag == MESSAGE_CHANGE) {
629                         this->_myDeserializer->deserializeEventChgAttr(part.data);
630                         msgcopy.erase(0, part.next_pos);
632                 } else if (part.tag == MESSAGE_NEWOBJ) {
633                         this->_myDeserializer->deserializeEventAdd(part.data);
634                         msgcopy.erase(0, part.next_pos);
636                 } else if (part.tag == MESSAGE_DELETE) {
637                         this->_myDeserializer->deserializeEventDel(part.data);
638                         msgcopy.erase(0, part.next_pos);
640                 } else if (part.tag == MESSAGE_DOCUMENT) {
641                         // no special handler, just keep going with the rest of the message
642                         msgcopy.erase(0, part.next_pos);
644                 } else if (part.tag == MESSAGE_NODECONTENT) {
645                         this->_myDeserializer->deserializeEventChgContent(part.data);
646                         msgcopy.erase(0, part.next_pos);
648                 } else if (part.tag == MESSAGE_ORDERCHANGE) {
649                         this->_myDeserializer->deserializeEventChgOrder(part.data);
650                         msgcopy.erase(0, part.next_pos);
652                 } else if (part.tag == MESSAGE_COMMIT) {
653                         // Retrieve the deserialized event log, node actions, and nodes with updated attributes
654                         XML::Event* log = this->_myDeserializer->getEventLog();
655                         KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
656                         AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
658                         // Make document insensitive to undo
659                         gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
660                         sp_document_set_undo_sensitive(this->_myDoc, FALSE);
662                         // Replay the log and push it onto the undo stack
663                         sp_repr_replay_log(log);
665                         // Call updateRepr on changed nodes
666                         // This is required for some tools to function properly, i.e. text tool
667                         // (TODO: we don't need to update _all_ changed nodes, just their parents)
668                         AttributesUpdatedSet::iterator i = updated.begin();
669                         for(; i != updated.end(); i++) {
670                                 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
671                                 if (updated) {
672                                         updated->updateRepr();
673                                 }
674                         }
676                         // merge the events generated by updateRepr
677                         sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
678                         this->_myDoc->priv->partial = NULL;
680                         this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
682                         // Restore undo sensitivity
683                         sp_document_set_undo_sensitive(this->_myDoc, saved);
685                         // Add or delete nodes to/from the tracker
686                         this->_myTracker->process(node_changes);
688                         // Reset deserializer state
689                         this->_myDeserializer->reset();
690                         break;
692                 } else if (part.tag == MESSAGE_UNDO) {
693                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
694                         sp_document_undo(this->_myDoc);
695                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
696                         msgcopy.erase(0, part.next_pos);
698                 } else if (part.tag == MESSAGE_REDO) {
699                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
700                         sp_document_redo(this->_myDoc);
701                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
702                         msgcopy.erase(0, part.next_pos);
704                 } else if (part.tag == MESSAGE_DOCBEGIN) {
705                         msgcopy.erase(0, part.next_pos);
707                 } else if (part.tag == MESSAGE_DOCEND) {
708                         // Set this to be the new original state of the document
709                         sp_document_done(this->document());
710                         sp_document_clear_redo(this->document());
711                         sp_document_clear_undo(this->document());
712                         this->setupCommitListener();
713                         msgcopy.erase(0, part.next_pos);
715                 } else {
716                         msgcopy.erase(0, part.next_pos);
718                 }
719         }
721         this->_log(changemsg);
723         this->_commitLog();
726 bool
727 SessionManager::isPlayingSessionFile()
729         return this->session_data->status[PLAYING_SESSION_FILE];
732 void
733 SessionManager::loadSessionFile(Glib::ustring filename)
735         if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
736                 try {
737                         if (this->_mySessionFile) {
738                                 delete this->_mySessionFile;
739                         }
740                         this->_mySessionFile = new SessionFile(filename, true, false);
742                         // Initialize objects needed for session playback
743                         if (this->_mySessionPlayer == NULL) {
744                                 this->_mySessionPlayer = new SessionFilePlayer(16, this);
745                         } else {
746                                 this->_mySessionPlayer->load(this->_mySessionFile);
747                         }
749                         if (this->_myTracker == NULL) {
750                                 this->_myTracker = new XMLNodeTracker(this);
751                         } else {
752                                 this->_myTracker->reset();
753                         }
755                         if (!this->session_data) {
756                                 this->session_data = new SessionData(this);
757                         }
759                         if (!this->_myDeserializer) {
760                                 this->_myDeserializer = new Deserializer(this->node_tracker());
761                         }
763                         if (!this->_myUndoObserver) {
764                                 this->setupCommitListener();
765                         }
767                         this->session_data->status.set(PLAYING_SESSION_FILE, 1);
770                 } catch (Glib::FileError e) {
771                         g_warning("Could not load session file: %s", e.what().data());
772                 }
773         }
776 void
777 SessionManager::userConnectedToWhiteboard(gchar const* JID)
779         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
783 void
784 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
787         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
789         // Inform the user
790         // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
791         // This message is not used in a chatroom context.
792         Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
793         // TRANSLATORS: %1 and %2 are userids
794         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));
796         // TODO: parent this dialog to the active desktop
797         Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
798         /*
799         dialog.set_message(primary, true);
800         dialog.set_secondary_text(secondary, true);
801         */
802         dialog.run();
805 void
806 SessionManager::startSendQueueDispatch()
808         this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
811 void
812 SessionManager::stopSendQueueDispatch()
814         if (this->_send_queue_dispatcher) {
815                 this->_send_queue_dispatcher.disconnect();
816         }
819 void
820 SessionManager::startReceiveQueueDispatch()
822         this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
825 void
826 SessionManager::stopReceiveQueueDispatch()
828         if (this->_receive_queue_dispatcher) {
829                 this->_receive_queue_dispatcher.disconnect();
830         }
833 void
834 SessionManager::clearDocument()
836         // clear all layers, definitions, and metadata
837         XML::Node* rroot = this->_myDoc->rroot;
839         // clear definitions
840         XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
841         g_assert(SP_ROOT(this->_myDoc->root)->defs);
843         for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
844                 defs->removeChild(child);
845         }
847         // clear layers
848         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
849                 if (strcmp(child->name(), "svg:g") == 0) {
850                         rroot->removeChild(child);
851                 }
852         }
854         // clear metadata
855         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
856                 if (strcmp(child->name(), "svg:metadata") == 0) {
857                         rroot->removeChild(child);
858                 }
859         }
861 //      sp_document_done(this->_myDoc);
864 void
865 SessionManager::setupInkscapeInterface()
867         this->session_data->status.set(IN_WHITEBOARD, 1);
868         this->startSendQueueDispatch();
869         this->startReceiveQueueDispatch();
870         if (!this->_myTracker) {
871                 this->_myTracker = new XMLNodeTracker(this);
872         }
874         this->_mySerializer = new Serializer(this->node_tracker());
875         this->_myDeserializer = new Deserializer(this->node_tracker());
877         // We're in a whiteboard session now, so set verb sensitivity accordingly
878         this->_setVerbSensitivity(ESTABLISHED_SESSION);
881 void
882 SessionManager::setupCommitListener()
884         this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
885         this->_myDoc->addUndoObserver(*this->_myUndoObserver);
888 ::SPDesktop*
889 SessionManager::desktop()
891         return this->_myDesktop;
894 ::SPDocument*
895 SessionManager::document()
897         return this->_myDoc;
900 Callbacks*
901 SessionManager::callbacks()
903         return this->_myCallbacks;
906 Whiteboard::UndoStackObserver*
907 SessionManager::undo_stack_observer()
909         return this->_myUndoObserver;
912 Serializer*
913 SessionManager::serializer()
915         return this->_mySerializer;
918 XMLNodeTracker*
919 SessionManager::node_tracker()
921         return this->_myTracker;
925 ChatMessageHandler*
926 SessionManager::chat_handler()
928         return this->_myChatHandler;
931 SessionFilePlayer*
932 SessionManager::session_player()
934         return this->_mySessionPlayer;
937 SessionFile*
938 SessionManager::session_file()
940         return this->_mySessionFile;
943 void
944 SessionManager::_log(Glib::ustring const& message)
946         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
947                 this->_mySessionFile->addMessage(message);
948         }
951 void
952 SessionManager::_commitLog()
954         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
955                 this->_mySessionFile->commit();
956         }
959 void
960 SessionManager::_closeLog()
962         if (this->_mySessionFile) {
963                 this->_mySessionFile->close();
964         }
967 void
968 SessionManager::startLog(Glib::ustring filename)
970         try {
971                 this->_mySessionFile = new SessionFile(filename, false, false);
972         } catch (Glib::FileError e) {
973                 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
974                 throw;
975         }
978 void
979 SessionManager::_tryToStartLog()
981         if (this->session_data) {
982                 if (!this->session_data->sessionFile.empty()) {
983                         bool undecided = true;
984                         while(undecided) {
985                                 try {
986                                         this->startLog(this->session_data->sessionFile);
987                                         undecided = false;
988                                 } catch (Glib::FileError e) {
989                                         undecided = true;
990                                         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());
991                                         Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
992                                         dlg.add_button(_("Choose a different location"), 0);
993                                         dlg.add_button(_("Skip session recording"), 1);
994                                         switch (dlg.run()) {
995                                                 case 0:
996                                                 {
997                                                         Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
998                                                         sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
999                                                         sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1000                                                         int result = sessionfiledlg.run();
1001                                                         switch (result) {
1002                                                                 case Gtk::RESPONSE_OK:
1003                                                                 {
1004                                                                         this->session_data->sessionFile = sessionfiledlg.get_filename();
1005                                                                         break;
1006                                                                 }
1007                                                                 case Gtk::RESPONSE_CANCEL:
1008                                                                 default:
1009                                                                         undecided = false;
1010                                                                         break;
1011                                                         }
1012                                                         break;
1013                                                 }
1014                                                 case 1:
1015                                                 default:
1016                                                         undecided = false;
1017                                                         break;
1018                                         }
1019                                 }
1020                         }
1021                 }
1022         }
1025 void
1026 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1028         return;
1030         switch (mode) {
1031                 case ESTABLISHED_CONNECTION:
1032                         // Upon successful connection, we can disconnect from the server.
1033                         // We can also start sharing a document with a user or chatroom.
1034                         // We cannot, however, connect to a new server without first disconnecting.
1035                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1036                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1037                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1038                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1039                         break;
1041                 case ESTABLISHED_SESSION:
1042                         // When we have established a session, we should not permit the user to go and
1043                         // establish another session from the same document without first disconnecting.
1044                         //
1045                         // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1046                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1047                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1048                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1049                         break;
1050                 case DISCONNECTED_FROM_SESSION:
1051                         // Upon disconnecting from a session, we can establish a new session and disconnect
1052                         // from the server, but we cannot disconnect from a session (since we just left it.)
1053                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1054                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1055                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1057                 case INITIAL:
1058                 default:
1059                         // Upon construction, there is no active connection, so we cannot do the following:
1060                         // (1) disconnect from a session
1061                         // (2) disconnect from a server
1062                         // (3) share with a user 
1063                         // (4) share with a chatroom
1064                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1065                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1066                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1067                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1068                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1069                         break;
1070         };
1073 /*
1074 void
1075 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1077         int type = event.getType();
1079         switch (type) {
1080                 case Pedro::XmppEvent::EVENT_STATUS:
1081                         break;
1082                 case Pedro::XmppEvent::EVENT_ERROR:
1083                         break;
1084                 case Pedro::XmppEvent::EVENT_CONNECTED:
1085                         break;
1086                 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1087                         break;
1088                 case Pedro::XmppEvent::EVENT_MESSAGE:
1089                         break;
1090                 case Pedro::XmppEvent::EVENT_PRESENCE:
1091                         break;
1092                 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1093                         break;
1094                 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1095                         break;
1096                 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1097                         break;
1098                 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1099                         break;
1100         }
1102 */
1109 /*
1110   Local Variables:
1111   mode:c++
1112   c-file-style:"stroustrup"
1113   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1114   indent-tabs-mode:nil
1115   fill-column:99
1116   End:
1117 */
1118 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :