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