ec26d20a95deeab0cbe261ee35b053f3efe36539
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 "prefs-utils.h"
28 #include "xml/repr.h"
29 #include "xml/node-observer.h"
31 #include "util/ucompose.hpp"
33 #include "message-context.h"
34 #include "message-stack.h"
35 #include "desktop-handles.h"
36 #include "document.h"
37 #include "document-private.h"
38 #include "verbs.h"
40 #include "jabber_whiteboard/defines.h"
41 #include "jabber_whiteboard/typedefs.h"
42 #include "jabber_whiteboard/deserializer.h"
43 #include "jabber_whiteboard/message-utilities.h"
44 #include "jabber_whiteboard/message-handler.h"
45 #include "jabber_whiteboard/node-tracker.h"
46 #include "jabber_whiteboard/jabber-handlers.h"
47 #include "jabber_whiteboard/callbacks.h"
48 #include "jabber_whiteboard/chat-handler.h"
49 #include "jabber_whiteboard/session-file.h"
50 #include "jabber_whiteboard/session-file-player.h"
51 #include "jabber_whiteboard/session-manager.h"
52 #include "jabber_whiteboard/message-aggregator.h"
53 #include "jabber_whiteboard/undo-stack-observer.h"
54 #include "jabber_whiteboard/serializer.h"
56 //#include "jabber_whiteboard/pedro/pedroxmpp.h"
58 #include "jabber_whiteboard/message-node.h"
59 #include "jabber_whiteboard/message-queue.h"
61 namespace Inkscape {
63 namespace Whiteboard {
65 SessionData::SessionData(SessionManager *sm)
66 {
67 this->_sm = sm;
68 this->recipient = NULL;
69 this->connection = NULL;
70 this->ssl = NULL;
71 this->ignoreFurtherSSLErrors = false;
72 this->send_queue = new SendMessageQueue(sm);
73 this->sequence_number = 1;
74 }
76 SessionData::~SessionData()
77 {
78 this->receive_queues.clear();
80 if (this->send_queue) {
81 delete this->send_queue;
82 }
83 }
85 SessionManager::SessionManager(::SPDesktop *desktop)
86 {
88 // Initialize private members to NULL to facilitate deletion in destructor
89 this->_myDoc = NULL;
90 this->session_data = NULL;
91 this->_myCallbacks = NULL;
92 this->_myTracker = NULL;
93 this->_myChatHandler = NULL;
94 this->_mySessionFile = NULL;
95 this->_mySessionPlayer = NULL;
96 this->_myMessageHandler = NULL;
97 this->_myUndoObserver = NULL;
98 this->_mySerializer = NULL;
99 this->_myDeserializer = NULL;
101 this->setDesktop(desktop);
103 if (this->_myDoc == NULL) {
104 g_error("Initializing SessionManager on null document object!");
105 }
108 #ifdef WIN32
109 //# lm_initialize() must be called before any network code
110 /*
111 if (!lm_initialize_called) {
112 lm_initialize();
113 lm_initialize_called = true;
114 }
115 */
116 #endif
118 this->_setVerbSensitivity(INITIAL);
119 }
121 SessionManager::~SessionManager()
122 {
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);
167 }
169 void
170 SessionManager::setDesktop(::SPDesktop* desktop)
171 {
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 }
180 }
182 int
183 SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
184 {
185 GError* error = NULL;
186 Glib::ustring jid;
188 // JID format is username@server/resource
189 jid += username + "@" + server;
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 g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened connection to %s at port %s. Connecting...",
216 server.c_str(), port.c_str());
218 if (usessl) {
219 if (lm_ssl_is_supported()) {
220 this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
222 lm_ssl_ref(this->session_data->ssl);
223 } else {
224 return SSL_INITIALIZATION_ERROR;
225 }
226 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
227 }
229 // Send authorization
230 lm_connection_set_jid(this->session_data->connection, jid.c_str());
232 // TODO:
233 // Asynchronous connection and authentication would be nice,
234 // but it's a huge mess of mixing C callbacks and C++ method calls.
235 // I've tried to do it and only managed to severely destabilize the Jabber
236 // server connection routines.
237 //
238 // This, of course, is an invitation to anyone more capable than me
239 // to convert this from synchronous to asynchronous Loudmouth calls.
240 if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
241 if (error != NULL) {
242 std::cout << "Failed to open: " << error->message << std::endl;
243 }
244 return FAILED_TO_CONNECT;
245 }
247 g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened Loudmouth connection in blocking mode.");
249 // Authenticate
250 if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
251 if (error != NULL) {
252 g_warning("Failed to authenticate: %s", error->message);
253 }
254 lm_connection_close(this->session_data->connection, NULL);
255 lm_connection_unref(this->session_data->connection);
256 this->session_data->connection = NULL;
257 return INVALID_AUTH;
258 }
260 g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
262 // Register message handler for presence messages
263 mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
264 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
266 // Register message handler for stream error messages
267 mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
268 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
270 // Register message handler for chat messages
271 mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
272 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
274 // Send presence message to server
275 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
276 if (!lm_connection_send(this->session_data->connection, m, &error)) {
277 if (error != NULL) {
278 g_warning("Presence message could not be sent: %s", error->message);
279 }
280 lm_connection_close(this->session_data->connection, NULL);
281 lm_connection_unref(this->session_data->connection);
282 this->session_data->connection = NULL;
283 return FAILED_TO_CONNECT;
284 }
286 this->session_data->status.set(LOGGED_IN, 1);
288 this->_myCallbacks = new Callbacks(this);
290 lm_message_unref(m);
292 this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
294 //On successful connect, remember info
295 prefs_set_string_attribute("whiteboard.server", "name", server.c_str());
296 prefs_set_string_attribute("whiteboard.server", "port", port.c_str());
297 prefs_set_string_attribute("whiteboard.server", "username", username.c_str());
298 prefs_set_int_attribute("whiteboard.server", "ssl", (usessl) ? 1 : 0);
299 //Option to store password here?
302 return CONNECT_SUCCESS;
303 }
305 LmSSLResponse
306 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
307 {
308 if (this->session_data->ignoreFurtherSSLErrors) {
309 return LM_SSL_RESPONSE_CONTINUE;
310 }
312 Glib::ustring msg;
314 // TODO: It'd be nice to provide the user with additional information in some cases,
315 // like fingerprints, hostname, etc.
316 switch(status) {
317 case LM_SSL_STATUS_NO_CERT_FOUND:
318 msg = _("No SSL certificate was found.");
319 break;
320 case LM_SSL_STATUS_UNTRUSTED_CERT:
321 msg = _("The SSL certificate provided by the Jabber server is untrusted.");
322 break;
323 case LM_SSL_STATUS_CERT_EXPIRED:
324 msg = _("The SSL certificate provided by the Jabber server is expired.");
325 break;
326 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
327 msg = _("The SSL certificate provided by the Jabber server has not been activated.");
328 break;
329 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
330 msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
331 break;
332 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
333 msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
334 break;
335 case LM_SSL_STATUS_GENERIC_ERROR:
336 msg = _("An unknown error occurred while setting up the SSL connection.");
337 break;
338 }
340 // TRANSLATORS: %1 is the message that describes the specific error that occurred when
341 // establishing the SSL connection.
342 Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
344 Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
345 dlg.add_button(_("Continue connecting and ignore further errors"), 0);
346 dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
347 dlg.add_button(_("Cancel connection"), 2);
349 switch(dlg.run()) {
350 case 0:
351 this->session_data->ignoreFurtherSSLErrors = true;
352 /* FALL-THROUGH */
353 case 1:
354 return LM_SSL_RESPONSE_CONTINUE;
356 default:
357 return LM_SSL_RESPONSE_STOP;
358 }
359 }
361 void
362 SessionManager::disconnectFromServer()
363 {
364 if (this->session_data->connection) {
365 GError* error = NULL;
367 LmMessage *m;
368 this->disconnectFromDocument();
369 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
370 if (!lm_connection_send(this->session_data->connection, m, &error)) {
371 g_warning("Could not send unavailable presence message: %s", error->message);
372 }
374 lm_message_unref(m);
375 lm_connection_close(this->session_data->connection, NULL);
376 lm_connection_unref(this->session_data->connection);
377 if (this->session_data->ssl) {
378 lm_ssl_unref(this->session_data->ssl);
379 }
381 this->session_data->connection = NULL;
382 this->session_data->ssl = NULL;
383 this->_setVerbSensitivity(INITIAL);
384 }
385 }
387 void
388 SessionManager::disconnectFromDocument()
389 {
390 if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
391 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
392 }
393 this->closeSession();
394 this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
395 }
397 void
398 SessionManager::closeSession()
399 {
401 if (this->session_data->status[IN_WHITEBOARD]) {
402 this->session_data->status.set(IN_WHITEBOARD, 0);
403 this->session_data->receive_queues.clear();
404 this->session_data->send_queue->clear();
405 this->stopSendQueueDispatch();
406 this->stopReceiveQueueDispatch();
407 }
409 if (this->_myUndoObserver) {
410 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
411 }
413 delete this->_myUndoObserver;
414 delete this->_mySerializer;
415 delete this->_myDeserializer;
417 this->_myUndoObserver = NULL;
418 this->_mySerializer = NULL;
419 this->_myDeserializer = NULL;
421 if (this->_myTracker) {
422 delete this->_myTracker;
423 this->_myTracker = NULL;
424 }
427 this->setRecipient(NULL);
428 }
430 void
431 SessionManager::setRecipient(char const* recipientJID)
432 {
433 if (this->session_data->recipient) {
434 free(const_cast< gchar* >(this->session_data->recipient));
435 }
437 if (recipientJID == NULL) {
438 this->session_data->recipient = NULL;
439 } else {
440 this->session_data->recipient = g_strdup(recipientJID);
441 }
442 }
444 void
445 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
446 {
447 if (!this->session_data->status[IN_WHITEBOARD]) {
448 return;
449 }
451 std::string& recipient = const_cast< std::string& >(recipientJID);
452 if (recipient.empty()) {
453 recipient = this->session_data->recipient;
454 }
457 switch (type) {
458 case DOCUMENT_BEGIN:
459 case DOCUMENT_END:
460 case CHANGE_NOT_REPEATABLE:
461 case CHANGE_REPEATABLE:
462 case CHANGE_COMMIT:
463 {
464 MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
465 this->session_data->send_queue->insert(newNode);
466 Inkscape::GC::release(newNode);
467 break;
468 }
469 default:
470 g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message. This may lead to desynchronization!");
471 break;
472 }
473 }
476 // FIXME:
477 // This method needs a massive, massive, massive overhaul.
478 int
479 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
480 {
481 LmMessage* m;
482 GError* error = NULL;
483 char* type, * seq;
485 if (recipientJID == NULL || recipientJID == "") {
486 g_warning("Null recipient JID specified; not sending message.");
487 return NO_RECIPIENT_JID;
488 } else {
489 }
491 // create message
492 m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
494 // add sender
495 lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
497 // set message subtype according to whether or not this is
498 // destined for a chatroom
499 if (chatroom) {
500 lm_message_node_set_attribute(m->node, "type", "groupchat");
501 } else {
502 lm_message_node_set_attribute(m->node, "type", "chat");
503 }
505 // set protocol version;
506 // we are currently fixed at version 1
507 lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
509 // add message type
510 type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
511 snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
512 lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
513 free(type);
515 // add message body
516 if (!msg.empty()) {
517 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
518 } else {
519 }
521 // add sequence number
522 switch(msgtype) {
523 case CHANGE_REPEATABLE:
524 case CHANGE_NOT_REPEATABLE:
525 case DUMMY_CHANGE:
526 case CHANGE_COMMIT:
527 case DOCUMENT_BEGIN:
528 case DOCUMENT_END:
529 case CONNECT_REQUEST_RESPONSE_CHAT:
530 case CONNECT_REQUEST_RESPONSE_USER:
531 case CHATROOM_SYNCHRONIZE_RESPONSE:
532 seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
533 sprintf(seq, "%u", sequence);
534 lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
535 free(seq);
536 break;
538 case CONNECT_REQUEST_USER:
539 case CONNECTED_SIGNAL:
540 case DISCONNECTED_FROM_USER_SIGNAL:
541 break;
543 // Error messages and synchronization requests do not need a sequence number
544 case CHATROOM_SYNCHRONIZE_REQUEST:
545 case CONNECT_REQUEST_REFUSED_BY_PEER:
546 case UNSUPPORTED_PROTOCOL_VERSION:
547 case ALREADY_IN_SESSION:
548 break;
550 default:
551 g_warning("Outgoing message type not recognized; not sending.");
552 lm_message_unref(m);
553 return UNKNOWN_OUTGOING_TYPE;
554 }
556 // We want to log messages even if they were not successfully sent,
557 // since the user may opt to re-synchronize a session using the session
558 // file record.
559 if (!msg.empty()) {
560 this->_log(msg);
561 this->_commitLog();
562 }
564 // send message
566 if (!lm_connection_send(this->session_data->connection, m, &error)) {
567 g_warning("Send failed: %s", error->message);
568 lm_message_unref(m);
569 return CONNECTION_ERROR;
570 }
572 lm_message_unref(m);
573 return SEND_SUCCESS;
574 }
576 void
577 SessionManager::connectionError(Glib::ustring const& errmsg)
578 {
579 Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
580 dlg.run();
581 // sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
582 }
584 void
585 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
586 {
587 Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
588 this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
590 Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
592 if(root == NULL) {
593 return;
594 }
596 NewChildObjectMessageList newchildren;
597 MessageAggregator& agg = MessageAggregator::instance();
598 Glib::ustring buf;
600 for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
601 // TODO: replace with Serializer methods
602 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
604 NewChildObjectMessageList::iterator j = newchildren.begin();
605 Glib::ustring aggbuf;
607 for(; j != newchildren.end(); j++) {
608 if (!agg.addOne(*j, aggbuf)) {
609 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
610 aggbuf.clear();
611 agg.addOne(*j, aggbuf);
612 }
613 }
615 // send remaining changes
616 if (!aggbuf.empty()) {
617 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
618 aggbuf.clear();
619 }
621 newchildren.clear();
622 buf.clear();
623 }
625 Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
626 this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
627 Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
628 this->sendChange(docend, DOCUMENT_END, recipientJID, false);
629 }
631 void
632 SessionManager::receiveChange(Glib::ustring const& changemsg)
633 {
635 struct Node part;
637 Glib::ustring msgcopy = changemsg.c_str();
640 while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
641 // TODO:
642 // Yikes. This is ugly.
643 if (part.tag == MESSAGE_CHANGE) {
644 this->_myDeserializer->deserializeEventChgAttr(part.data);
645 msgcopy.erase(0, part.next_pos);
647 } else if (part.tag == MESSAGE_NEWOBJ) {
648 this->_myDeserializer->deserializeEventAdd(part.data);
649 msgcopy.erase(0, part.next_pos);
651 } else if (part.tag == MESSAGE_DELETE) {
652 this->_myDeserializer->deserializeEventDel(part.data);
653 msgcopy.erase(0, part.next_pos);
655 } else if (part.tag == MESSAGE_DOCUMENT) {
656 // no special handler, just keep going with the rest of the message
657 msgcopy.erase(0, part.next_pos);
659 } else if (part.tag == MESSAGE_NODECONTENT) {
660 this->_myDeserializer->deserializeEventChgContent(part.data);
661 msgcopy.erase(0, part.next_pos);
663 } else if (part.tag == MESSAGE_ORDERCHANGE) {
664 this->_myDeserializer->deserializeEventChgOrder(part.data);
665 msgcopy.erase(0, part.next_pos);
667 } else if (part.tag == MESSAGE_COMMIT) {
668 // Retrieve the deserialized event log, node actions, and nodes with updated attributes
669 XML::Event* log = this->_myDeserializer->getEventLog();
670 KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
671 AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
673 // Make document insensitive to undo
674 gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
675 sp_document_set_undo_sensitive(this->_myDoc, FALSE);
677 // Replay the log and push it onto the undo stack
678 sp_repr_replay_log(log);
680 // Call updateRepr on changed nodes
681 // This is required for some tools to function properly, i.e. text tool
682 // (TODO: we don't need to update _all_ changed nodes, just their parents)
683 AttributesUpdatedSet::iterator i = updated.begin();
684 for(; i != updated.end(); i++) {
685 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
686 if (updated) {
687 updated->updateRepr();
688 }
689 }
691 // merge the events generated by updateRepr
692 sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
693 this->_myDoc->priv->partial = NULL;
695 this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
697 // Restore undo sensitivity
698 sp_document_set_undo_sensitive(this->_myDoc, saved);
700 // Add or delete nodes to/from the tracker
701 this->_myTracker->process(node_changes);
703 // Reset deserializer state
704 this->_myDeserializer->reset();
705 break;
707 } else if (part.tag == MESSAGE_UNDO) {
708 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
709 sp_document_undo(this->_myDoc);
710 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
711 msgcopy.erase(0, part.next_pos);
713 } else if (part.tag == MESSAGE_REDO) {
714 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
715 sp_document_redo(this->_myDoc);
716 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
717 msgcopy.erase(0, part.next_pos);
719 } else if (part.tag == MESSAGE_DOCBEGIN) {
720 msgcopy.erase(0, part.next_pos);
722 } else if (part.tag == MESSAGE_DOCEND) {
723 // Set this to be the new original state of the document
724 sp_document_done(this->document());
725 sp_document_clear_redo(this->document());
726 sp_document_clear_undo(this->document());
727 this->setupCommitListener();
728 msgcopy.erase(0, part.next_pos);
730 } else {
731 msgcopy.erase(0, part.next_pos);
733 }
734 }
736 this->_log(changemsg);
738 this->_commitLog();
739 }
741 bool
742 SessionManager::isPlayingSessionFile()
743 {
744 return this->session_data->status[PLAYING_SESSION_FILE];
745 }
747 void
748 SessionManager::loadSessionFile(Glib::ustring filename)
749 {
750 if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
751 try {
752 if (this->_mySessionFile) {
753 delete this->_mySessionFile;
754 }
755 this->_mySessionFile = new SessionFile(filename, true, false);
757 // Initialize objects needed for session playback
758 if (this->_mySessionPlayer == NULL) {
759 this->_mySessionPlayer = new SessionFilePlayer(16, this);
760 } else {
761 this->_mySessionPlayer->load(this->_mySessionFile);
762 }
764 if (this->_myTracker == NULL) {
765 this->_myTracker = new XMLNodeTracker(this);
766 } else {
767 this->_myTracker->reset();
768 }
770 if (!this->session_data) {
771 this->session_data = new SessionData(this);
772 }
774 if (!this->_myDeserializer) {
775 this->_myDeserializer = new Deserializer(this->node_tracker());
776 }
778 if (!this->_myUndoObserver) {
779 this->setupCommitListener();
780 }
782 this->session_data->status.set(PLAYING_SESSION_FILE, 1);
785 } catch (Glib::FileError e) {
786 g_warning("Could not load session file: %s", e.what().data());
787 }
788 }
789 }
791 void
792 SessionManager::userConnectedToWhiteboard(gchar const* JID)
793 {
794 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
795 }
798 void
799 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
800 {
802 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
804 // Inform the user
805 // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
806 // This message is not used in a chatroom context.
807 Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
808 // TRANSLATORS: %1 and %2 are userids
809 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));
811 // TODO: parent this dialog to the active desktop
812 Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
813 /*
814 dialog.set_message(primary, true);
815 dialog.set_secondary_text(secondary, true);
816 */
817 dialog.run();
818 }
820 void
821 SessionManager::startSendQueueDispatch()
822 {
823 this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
824 }
826 void
827 SessionManager::stopSendQueueDispatch()
828 {
829 if (this->_send_queue_dispatcher) {
830 this->_send_queue_dispatcher.disconnect();
831 }
832 }
834 void
835 SessionManager::startReceiveQueueDispatch()
836 {
837 this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
838 }
840 void
841 SessionManager::stopReceiveQueueDispatch()
842 {
843 if (this->_receive_queue_dispatcher) {
844 this->_receive_queue_dispatcher.disconnect();
845 }
846 }
848 void
849 SessionManager::clearDocument()
850 {
851 // clear all layers, definitions, and metadata
852 XML::Node* rroot = this->_myDoc->rroot;
854 // clear definitions
855 XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
856 g_assert(SP_ROOT(this->_myDoc->root)->defs);
858 for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
859 defs->removeChild(child);
860 }
862 // clear layers
863 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
864 if (strcmp(child->name(), "svg:g") == 0) {
865 rroot->removeChild(child);
866 }
867 }
869 // clear metadata
870 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
871 if (strcmp(child->name(), "svg:metadata") == 0) {
872 rroot->removeChild(child);
873 }
874 }
876 // sp_document_done(this->_myDoc);
877 }
879 void
880 SessionManager::setupInkscapeInterface()
881 {
882 this->session_data->status.set(IN_WHITEBOARD, 1);
883 this->startSendQueueDispatch();
884 this->startReceiveQueueDispatch();
885 if (!this->_myTracker) {
886 this->_myTracker = new XMLNodeTracker(this);
887 }
889 this->_mySerializer = new Serializer(this->node_tracker());
890 this->_myDeserializer = new Deserializer(this->node_tracker());
892 // We're in a whiteboard session now, so set verb sensitivity accordingly
893 this->_setVerbSensitivity(ESTABLISHED_SESSION);
894 }
896 void
897 SessionManager::setupCommitListener()
898 {
899 this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
900 this->_myDoc->addUndoObserver(*this->_myUndoObserver);
901 }
903 ::SPDesktop*
904 SessionManager::desktop()
905 {
906 return this->_myDesktop;
907 }
909 ::SPDocument*
910 SessionManager::document()
911 {
912 return this->_myDoc;
913 }
915 Callbacks*
916 SessionManager::callbacks()
917 {
918 return this->_myCallbacks;
919 }
921 Whiteboard::UndoStackObserver*
922 SessionManager::undo_stack_observer()
923 {
924 return this->_myUndoObserver;
925 }
927 Serializer*
928 SessionManager::serializer()
929 {
930 return this->_mySerializer;
931 }
933 XMLNodeTracker*
934 SessionManager::node_tracker()
935 {
936 return this->_myTracker;
937 }
940 ChatMessageHandler*
941 SessionManager::chat_handler()
942 {
943 return this->_myChatHandler;
944 }
946 SessionFilePlayer*
947 SessionManager::session_player()
948 {
949 return this->_mySessionPlayer;
950 }
952 SessionFile*
953 SessionManager::session_file()
954 {
955 return this->_mySessionFile;
956 }
958 void
959 SessionManager::_log(Glib::ustring const& message)
960 {
961 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
962 this->_mySessionFile->addMessage(message);
963 }
964 }
966 void
967 SessionManager::_commitLog()
968 {
969 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
970 this->_mySessionFile->commit();
971 }
972 }
974 void
975 SessionManager::_closeLog()
976 {
977 if (this->_mySessionFile) {
978 this->_mySessionFile->close();
979 }
980 }
982 void
983 SessionManager::startLog(Glib::ustring filename)
984 {
985 try {
986 this->_mySessionFile = new SessionFile(filename, false, false);
987 } catch (Glib::FileError e) {
988 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
989 throw;
990 }
991 }
993 void
994 SessionManager::_tryToStartLog()
995 {
996 if (this->session_data) {
997 if (!this->session_data->sessionFile.empty()) {
998 bool undecided = true;
999 while(undecided) {
1000 try {
1001 this->startLog(this->session_data->sessionFile);
1002 undecided = false;
1003 } catch (Glib::FileError e) {
1004 undecided = true;
1005 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());
1006 Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
1007 dlg.add_button(_("Choose a different location"), 0);
1008 dlg.add_button(_("Skip session recording"), 1);
1009 switch (dlg.run()) {
1010 case 0:
1011 {
1012 Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1013 sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1014 sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1015 int result = sessionfiledlg.run();
1016 switch (result) {
1017 case Gtk::RESPONSE_OK:
1018 {
1019 this->session_data->sessionFile = sessionfiledlg.get_filename();
1020 break;
1021 }
1022 case Gtk::RESPONSE_CANCEL:
1023 default:
1024 undecided = false;
1025 break;
1026 }
1027 break;
1028 }
1029 case 1:
1030 default:
1031 undecided = false;
1032 break;
1033 }
1034 }
1035 }
1036 }
1037 }
1038 }
1040 void
1041 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1042 {
1043 return;
1045 switch (mode) {
1046 case ESTABLISHED_CONNECTION:
1047 // Upon successful connection, we can disconnect from the server.
1048 // We can also start sharing a document with a user or chatroom.
1049 // We cannot, however, connect to a new server without first disconnecting.
1050 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1051 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1052 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1053 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1054 break;
1056 case ESTABLISHED_SESSION:
1057 // When we have established a session, we should not permit the user to go and
1058 // establish another session from the same document without first disconnecting.
1059 //
1060 // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1061 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1062 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1063 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1064 break;
1065 case DISCONNECTED_FROM_SESSION:
1066 // Upon disconnecting from a session, we can establish a new session and disconnect
1067 // from the server, but we cannot disconnect from a session (since we just left it.)
1068 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1069 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1070 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1072 case INITIAL:
1073 default:
1074 // Upon construction, there is no active connection, so we cannot do the following:
1075 // (1) disconnect from a session
1076 // (2) disconnect from a server
1077 // (3) share with a user
1078 // (4) share with a chatroom
1079 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1080 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1081 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1082 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1083 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1084 break;
1085 };
1086 }
1088 /*
1089 void
1090 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1091 {
1092 int type = event.getType();
1094 switch (type) {
1095 case Pedro::XmppEvent::EVENT_STATUS:
1096 break;
1097 case Pedro::XmppEvent::EVENT_ERROR:
1098 break;
1099 case Pedro::XmppEvent::EVENT_CONNECTED:
1100 break;
1101 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1102 break;
1103 case Pedro::XmppEvent::EVENT_MESSAGE:
1104 break;
1105 case Pedro::XmppEvent::EVENT_PRESENCE:
1106 break;
1107 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1108 break;
1109 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1110 break;
1111 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1112 break;
1113 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1114 break;
1115 }
1116 }
1117 */
1119 }
1121 }
1124 /*
1125 Local Variables:
1126 mode:c++
1127 c-file-style:"stroustrup"
1128 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1129 indent-tabs-mode:nil
1130 fill-column:99
1131 End:
1132 */
1133 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :