1 /**
2 * Whiteboard session manager
3 *
4 * Authors:
5 * David Yip <yipdw@rose-hulman.edu>
6 * Bob Jamison (Pedro port)
7 * Abhishek Sharma
8 *
9 * Copyright (c) 2005 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #include <functional>
15 #include <algorithm>
16 #include <iostream>
17 #include <time.h>
19 #include <gtkmm.h>
20 #include <glibmm/i18n.h>
22 #include "xml/node.h"
23 #include "xml/repr.h"
25 #include "util/ucompose.hpp"
27 #include "xml/node-observer.h"
29 #include "pedro/pedrodom.h"
31 #include "ui/view/view-widget.h"
33 #include "document-private.h"
34 #include "interface.h"
35 #include "sp-namedview.h"
36 #include "document.h"
37 #include "desktop.h"
38 #include "desktop-handles.h"
40 #include "jabber_whiteboard/invitation-confirm-dialog.h"
41 #include "jabber_whiteboard/message-verifier.h"
42 #include "jabber_whiteboard/session-manager.h"
43 #include "jabber_whiteboard/inkboard-document.h"
44 #include "jabber_whiteboard/defines.h"
46 #include "jabber_whiteboard/dialog/choose-desktop.h"
48 namespace Inkscape {
50 namespace Whiteboard {
52 //#########################################################################
53 //# S E S S I O N M A N A G E R
54 //#########################################################################
56 SessionManager *sessionManagerInstance = NULL;
58 void SessionManager::showClient()
59 {
60 SessionManager::instance().gui.show();
61 }
63 SessionManager &SessionManager::instance()
64 {
65 if (!sessionManagerInstance) {
66 sessionManagerInstance = new SessionManager();
67 }
68 return *sessionManagerInstance;
69 }
71 SessionManager::SessionManager()
72 {
73 getClient().addXmppEventListener(*this);
75 this->CheckPendingInvitations =
76 Glib::signal_timeout().connect(sigc::mem_fun(
77 *this, &SessionManager::checkInvitationQueue), 50);
78 }
80 SessionManager::~SessionManager()
81 {
82 getClient().removeXmppEventListener(*this);
83 getClient().disconnect();
84 }
86 /**
87 * Initiates a shared session with a user or conference room.
88 *
89 * \param to The recipient to which this desktop will be linked, specified as a JID.
90 * \param type Type of the session; i.e. private message or group chat.
91 */
92 void
93 SessionManager::initialiseSession(Glib::ustring const& to, State::SessionType type)
94 {
96 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
97 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
98 if(inkdoc == NULL) return;
100 if(type == State::WHITEBOARD_PEER)
101 {
102 ChooseDesktop dialog;
103 int result = dialog.run();
105 if(result == Gtk::RESPONSE_OK)
106 {
107 SPDesktop *desktop = dialog.getDesktop();
109 if(desktop != NULL)
110 {
111 Inkscape::XML::Document *old_doc =
112 sp_desktop_document(desktop)->rdoc;
113 inkdoc->root()->mergeFrom(old_doc->root(),"id");
114 }
115 }else { return; }
116 }
118 char * sessionId = createSessionId(10);
120 inkdoc->setSessionId(sessionId);
122 makeInkboardDesktop(doc);
123 addSession(WhiteboardRecord(sessionId, inkdoc));
125 inkdoc->startSessionNegotiation();
128 }
130 void
131 SessionManager::terminateSession(Glib::ustring const& sessionId)
132 {
133 WhiteboardList::iterator i = whiteboards.begin();
134 for(; i != whiteboards.end(); ++i) {
135 if ((*i).first == sessionId)
136 break;
137 }
139 if (i != whiteboards.end()) {
140 (*i).second->terminateSession();
141 whiteboards.erase(i);
142 }
143 }
145 void
146 SessionManager::addSession(WhiteboardRecord whiteboard)
147 {
148 whiteboards.push_back(whiteboard);
149 }
151 InkboardDocument*
152 SessionManager::getInkboardSession(Glib::ustring const& sessionId)
153 {
154 WhiteboardList::iterator i = whiteboards.begin();
155 for(; i != whiteboards.end(); ++i) {
156 if ((*i).first == sessionId) {
157 return (*i).second;
158 }
159 }
160 return NULL;
161 }
163 void
164 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
165 {
166 int type = event.getType();
168 switch (type) {
169 case Pedro::XmppEvent::EVENT_STATUS:
170 {
171 break;
172 }
173 case Pedro::XmppEvent::EVENT_ERROR:
174 {
175 break;
176 }
177 case Pedro::XmppEvent::EVENT_CONNECTED:
178 {
179 break;
180 }
181 case Pedro::XmppEvent::EVENT_DISCONNECTED:
182 {
183 break;
184 }
185 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
186 case Pedro::XmppEvent::EVENT_MESSAGE:
187 {
188 Pedro::Element *root = event.getDOM();
190 if (root && root->getTagAttribute("wb", "xmlns") == Vars::INKBOARD_XMLNS)
191 processWhiteboardEvent(event);
193 break;
194 }
195 case Pedro::XmppEvent::EVENT_PRESENCE:
196 {
197 break;
198 }
199 case Pedro::XmppEvent::EVENT_MUC_JOIN:
200 {
201 break;
202 }
203 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
204 {
205 break;
206 }
207 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
208 {
209 break;
210 }
211 default:
212 {
213 break;
214 }
215 }
216 }
218 /**
219 * Handles all incoming messages from pedro within a valid namespace, CONNECT_REQUEST messages
220 * are handled here, as they have no InkboardDocument to be handled from, all other messages
221 * are passed to their appropriate Inkboard document, which is identified by the 'session'
222 * attribute of the 'wb' element
223 *
224 */
225 void
226 SessionManager::processWhiteboardEvent(Pedro::XmppEvent const& event)
227 {
228 Pedro::Element* root = event.getDOM();
229 if (root == NULL) {
230 g_warning("Received null DOM; ignoring message.");
231 return;
232 }
234 Pedro::DOMString session = root->getTagAttribute("wb", "session");
235 Pedro::DOMString type = root->getTagAttribute("message", "type");
236 Pedro::DOMString domwrapper = root->getFirstChild()->getFirstChild()->getFirstChild()->getName();
238 if (session.empty()) {
239 g_warning("Received incomplete Whiteboard message, missing session identifier; ignoring message.");
240 return;
241 }
243 if(root->exists(Message::CONNECT_REQUEST) && type == State::WHITEBOARD_PEER)
244 {
245 handleIncomingInvitation(Invitation(event.getFrom(),session));
247 }else
248 {
249 Message::Wrapper wrapper = static_cast< Message::Wrapper >(domwrapper);
250 InkboardDocument* doc = getInkboardSession(session);
252 if(doc != NULL)
253 doc->recieve(wrapper, root->getFirstChild());
254 }
255 }
257 char*
258 SessionManager::createSessionId(int size)
259 {
260 // Create a random session identifier
261 char * randomString = (char*) malloc (size);
262 for (int n=0; n<size; n++)
263 randomString[n]=rand()%26+'a';
264 randomString[size+1]='\0';
266 return randomString;
267 }
269 /**
270 * Adds an invitation to a queue to be executed in SessionManager::_checkInvitationQueue()
271 * as when this method is called, we're still executing in Pedro's context, which causes
272 * issues when we run a dialog main loop.
273 *
274 */
275 void
276 SessionManager::handleIncomingInvitation(Invitation invitation)
277 {
278 // don't insert duplicate invitations
279 if (std::find(invitations.begin(),invitations.end(),invitation) != invitations.end())
280 return;
282 invitations.push_back(invitation);
284 }
286 bool
287 SessionManager::checkInvitationQueue()
288 {
289 // The user is currently busy with an action. Defer invitation processing
290 // until the user is free.
291 int x, y;
292 Gdk::ModifierType mt;
293 Gdk::Display::get_default()->get_pointer(x, y, mt);
294 if (mt & GDK_BUTTON1_MASK)
295 return true;
297 if (invitations.size() > 0)
298 {
299 // There's an invitation to process; process it.
300 Invitation invitation = invitations.front();
301 Glib::ustring from = invitation.first;
302 Glib::ustring sessionId = invitation.second;
304 Glib::ustring primary =
305 "<span weight=\"bold\" size=\"larger\">" +
306 String::ucompose(_("<b>%1</b> has invited you to a whiteboard session."), from) +
307 "</span>\n\n" +
308 String::ucompose(_("Do you wish to accept <b>%1</b>'s whiteboard session invitation?"), from);
310 InvitationConfirmDialog dialog(primary);
312 dialog.add_button(_("Accept invitation"), Dialog::ACCEPT_INVITATION);
313 dialog.add_button(_("Decline invitation"), Dialog::DECLINE_INVITATION);
315 Dialog::DialogReply reply = static_cast< Dialog::DialogReply >(dialog.run());
318 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", State::WHITEBOARD_PEER, from);
320 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
321 if(inkdoc == NULL) return true;
323 inkdoc->handleState(State::INITIAL,State::CONNECTING);
324 inkdoc->setSessionId(sessionId);
325 addSession(WhiteboardRecord(sessionId, inkdoc));
327 switch (reply) {
329 case Dialog::ACCEPT_INVITATION:{
330 inkdoc->send(from, Message::PROTOCOL,Message::ACCEPT_INVITATION);
331 makeInkboardDesktop(doc);
332 break; }
334 case Dialog::DECLINE_INVITATION: default: {
335 inkdoc->send(from, Message::PROTOCOL,Message::DECLINE_INVITATION);
336 terminateSession(sessionId);
337 break; }
338 }
340 invitations.pop_front();
342 }
344 return true;
345 }
347 //#########################################################################
348 //# HELPER FUNCTIONS
349 //#########################################################################
351 SPDocument*
352 makeInkboardDocument(int code, gchar const* rootname, State::SessionType type, Glib::ustring const& to)
353 {
354 SPDocument* doc;
356 InkboardDocument* rdoc = new InkboardDocument(g_quark_from_static_string("xml"), type, to);
357 rdoc->setAttribute("version", "1.0");
358 rdoc->setAttribute("standalone", "no");
359 XML::Node *comment = rdoc->createComment(" Created with Inkscape (http://www.inkscape.org/) ");
360 rdoc->appendChild(comment);
361 GC::release(comment);
363 XML::Node* root = rdoc->createElement(rootname);
364 rdoc->appendChild(root);
365 GC::release(root);
367 Glib::ustring name = String::ucompose(
368 _("Inkboard session (%1 to %2)"), SessionManager::instance().getClient().getJid(), to);
370 doc = SPDocument::createDoc(rdoc, NULL, NULL, name.c_str(), TRUE);
371 g_return_val_if_fail(doc != NULL, NULL);
373 return doc;
374 }
376 // TODO: When the switchover to the new GUI is complete, this function should go away
377 // and be replaced with a call to Inkscape::NSApplication::Editor::createDesktop.
378 // It currently only exists to correctly mimic the desktop creation functionality
379 // in file.cpp.
380 //
381 // \see sp_file_new
382 SPDesktop *makeInkboardDesktop(SPDocument* doc)
383 {
384 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
385 g_return_val_if_fail(dtw != NULL, NULL);
386 doc->doUnref();
388 sp_create_window(dtw, TRUE);
389 SPDesktop *dt = static_cast<SPDesktop*>(dtw->view);
390 sp_namedview_window_from_document(dt);
391 sp_namedview_update_layers_from_document(dt);
393 return dt;
394 }
396 } // namespace Whiteboard
398 } // namespace Inkscape
401 /*
402 Local Variables:
403 mode:c++
404 c-file-style:"stroustrup"
405 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
406 indent-tabs-mode:nil
407 fill-column:99
408 End:
409 */
410 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :