Wt examples  3.2.2
/home/koen/project/wt/public-git/wt/examples/simplechat/SimpleChatWidget.C
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 Emweb bvba, Heverlee, Belgium.
00003  *
00004  * See the LICENSE file for terms of use.
00005  */
00006 
00007 #include "SimpleChatWidget.h"
00008 #include "SimpleChatServer.h"
00009 
00010 #include <Wt/WApplication>
00011 #include <Wt/WContainerWidget>
00012 #include <Wt/WEnvironment>
00013 #include <Wt/WHBoxLayout>
00014 #include <Wt/WVBoxLayout>
00015 #include <Wt/WLabel>
00016 #include <Wt/WLineEdit>
00017 #include <Wt/WText>
00018 #include <Wt/WTextArea>
00019 #include <Wt/WPushButton>
00020 #include <Wt/WCheckBox>
00021 
00022 #include <iostream>
00023 
00024 using namespace Wt;
00025 
00026 SimpleChatWidget::SimpleChatWidget(SimpleChatServer& server,
00027                                    Wt::WContainerWidget *parent)
00028   : WContainerWidget(parent),
00029     server_(server),
00030     loggedIn_(false),
00031     userList_(0),
00032     messageReceived_(0)
00033 {
00034   user_ = server_.suggestGuest();
00035   letLogin();
00036 }
00037 
00038 SimpleChatWidget::~SimpleChatWidget()
00039 {
00040   delete messageReceived_;
00041   logout();
00042   disconnect();
00043 }
00044 
00045 void SimpleChatWidget::connect()
00046 {
00047   if (server_.connect
00048       (this, boost::bind(&SimpleChatWidget::processChatEvent, this, _1)))
00049     Wt::WApplication::instance()->enableUpdates(true);
00050 }
00051 
00052 void SimpleChatWidget::disconnect()
00053 {
00054   if (server_.disconnect(this))
00055     Wt::WApplication::instance()->enableUpdates(false);
00056 }
00057 
00058 void SimpleChatWidget::letLogin()
00059 {
00060   disconnect();
00061 
00062   clear();
00063 
00064   WVBoxLayout *vLayout = new WVBoxLayout();
00065   setLayout(vLayout);
00066 
00067   WHBoxLayout *hLayout = new WHBoxLayout();
00068   vLayout->addLayout(hLayout, 0, AlignTop | AlignLeft);
00069 
00070   hLayout->addWidget(new WLabel("User name:"), 0, AlignMiddle);
00071   hLayout->addWidget(userNameEdit_ = new WLineEdit(user_), 0, AlignMiddle);
00072   userNameEdit_->setFocus();
00073 
00074   WPushButton *b = new WPushButton("Login");
00075   hLayout->addWidget(b, 0, AlignMiddle);
00076 
00077   b->clicked().connect(this, &SimpleChatWidget::login);
00078   userNameEdit_->enterPressed().connect(this, &SimpleChatWidget::login);
00079 
00080   vLayout->addWidget(statusMsg_ = new WText());
00081   statusMsg_->setTextFormat(PlainText);
00082 }
00083 
00084 void SimpleChatWidget::login()
00085 {
00086   if (!loggedIn()) {
00087     WString name = userNameEdit_->text();
00088 
00089     if (!messageReceived_)
00090       messageReceived_ = new WSound("sounds/message_received.mp3");
00091 
00092     if (!startChat(name))
00093       statusMsg_->setText("Sorry, name '" + escapeText(name) +
00094                           "' is already taken.");
00095   }
00096 }
00097 
00098 void SimpleChatWidget::logout()
00099 {
00100   if (loggedIn()) {
00101     loggedIn_ = false;
00102     server_.logout(user_);
00103 
00104     letLogin();
00105   }
00106 }
00107 
00108 void SimpleChatWidget::createLayout(WWidget *messages, WWidget *userList,
00109                                     WWidget *messageEdit,
00110                                     WWidget *sendButton, WWidget *logoutButton)
00111 {
00112   /*
00113    * Create a vertical layout, which will hold 3 rows,
00114    * organized like this:
00115    *
00116    * WVBoxLayout
00117    * --------------------------------------------
00118    * | nested WHBoxLayout (vertical stretch=1)  |
00119    * |                              |           |
00120    * |  messages                    | userList  |
00121    * |   (horizontal stretch=1)     |           |
00122    * |                              |           |
00123    * --------------------------------------------
00124    * | message edit area                        |
00125    * --------------------------------------------
00126    * | WHBoxLayout                              |
00127    * | send | logout                            |
00128    * --------------------------------------------
00129    */
00130   WVBoxLayout *vLayout = new WVBoxLayout();
00131 
00132   // Create a horizontal layout for the messages | userslist.
00133   WHBoxLayout *hLayout = new WHBoxLayout();
00134 
00135   // Add widget to horizontal layout with stretch = 1
00136   hLayout->addWidget(messages, 1);
00137   messages->setStyleClass("chat-msgs");
00138 
00139     // Add another widget to horizontal layout with stretch = 0
00140   hLayout->addWidget(userList);
00141   userList->setStyleClass("chat-users");
00142 
00143   hLayout->setResizable(0, true);
00144 
00145   // Add nested layout to vertical layout with stretch = 1
00146   vLayout->addLayout(hLayout, 1);
00147 
00148   // Add widget to vertical layout with stretch = 0
00149   vLayout->addWidget(messageEdit);
00150   messageEdit->setStyleClass("chat-noedit");
00151 
00152   // Create a horizontal layout for the buttons.
00153   hLayout = new WHBoxLayout();
00154 
00155   // Add button to horizontal layout with stretch = 0
00156   hLayout->addWidget(sendButton);
00157 
00158   // Add button to horizontal layout with stretch = 0
00159   hLayout->addWidget(logoutButton);
00160 
00161   // Add nested layout to vertical layout with stretch = 0
00162   vLayout->addLayout(hLayout, 0, AlignLeft);
00163 
00164   setLayout(vLayout);
00165 }
00166 
00167 bool SimpleChatWidget::loggedIn() const
00168 {
00169   return loggedIn_;
00170 }
00171 
00172 void SimpleChatWidget::render(WFlags<RenderFlag> flags)
00173 {
00174   if (flags & RenderFull) {
00175     if (loggedIn()) {
00176       /* Handle a page refresh correctly */
00177       messageEdit_->setText(WString::Empty);
00178       doJavaScript("setTimeout(function() { "
00179                    + messages_->jsRef() + ".scrollTop += "
00180                    + messages_->jsRef() + ".scrollHeight;}, 0);");
00181     }
00182   }
00183 
00184   WContainerWidget::render(flags);
00185 }
00186 
00187 bool SimpleChatWidget::startChat(const WString& user)
00188 {
00189   /*
00190    * When logging in, we pass our processChatEvent method as the function that
00191    * is used to indicate a new chat event for this user.
00192    */
00193   if (server_.login(user)) {
00194     loggedIn_ = true;
00195     connect();
00196 
00197     user_ = user;    
00198 
00199     clear();
00200     userNameEdit_ = 0;
00201 
00202     messages_ = new WContainerWidget();
00203     userList_ = new WContainerWidget();
00204     messageEdit_ = new WTextArea();
00205     messageEdit_->setRows(2);
00206     messageEdit_->setFocus();
00207 
00208     // Display scroll bars if contents overflows
00209     messages_->setOverflow(WContainerWidget::OverflowAuto);
00210     userList_->setOverflow(WContainerWidget::OverflowAuto);
00211 
00212     sendButton_ = new WPushButton("Send");
00213     WPushButton *logoutButton = new WPushButton("Logout");
00214 
00215     createLayout(messages_, userList_, messageEdit_, sendButton_, logoutButton);
00216 
00217     /*
00218      * Connect event handlers:
00219      *  - click on button
00220      *  - enter in text area
00221      *
00222      * We will clear the input field using a small custom client-side
00223      * JavaScript invocation.
00224      */
00225 
00226     // Create a JavaScript 'slot' (JSlot). The JavaScript slot always takes
00227     // 2 arguments: the originator of the event (in our case the
00228     // button or text area), and the JavaScript event object.
00229     clearInput_.setJavaScript
00230       ("function(o, e) { setTimeout(function() {"
00231        "" + messageEdit_->jsRef() + ".value='';"
00232        "}, 0); }");
00233 
00234     // Bind the C++ and JavaScript event handlers.
00235     sendButton_->clicked().connect(this, &SimpleChatWidget::send);
00236     messageEdit_->enterPressed().connect(this, &SimpleChatWidget::send);
00237     sendButton_->clicked().connect(clearInput_);
00238     messageEdit_->enterPressed().connect(clearInput_);
00239     sendButton_->clicked().connect(messageEdit_, &WLineEdit::setFocus);
00240     messageEdit_->enterPressed().connect(messageEdit_, &WLineEdit::setFocus);
00241 
00242     // Prevent the enter from generating a new line, which is its default
00243     // action
00244     messageEdit_->enterPressed().preventDefaultAction();
00245 
00246     logoutButton->clicked().connect(this, &SimpleChatWidget::logout);
00247 
00248     WText *msg = new WText
00249       ("<div><span class='chat-info'>You are joining as "
00250        + escapeText(user_) + ".</span></div>",
00251        messages_);
00252     msg->setStyleClass("chat-msg");
00253 
00254     if (!userList_->parent()) {
00255       delete userList_;
00256       userList_ = 0;
00257     }
00258 
00259     if (!sendButton_->parent()) {
00260       delete sendButton_;
00261       sendButton_ = 0;
00262     }
00263 
00264     if (!logoutButton->parent())
00265       delete logoutButton;
00266 
00267     updateUsers();
00268     
00269     return true;
00270   } else
00271     return false;
00272 }
00273 
00274 void SimpleChatWidget::send()
00275 {
00276   if (!messageEdit_->text().empty())
00277     server_.sendMessage(user_, messageEdit_->text());
00278 }
00279 
00280 void SimpleChatWidget::updateUsers()
00281 {
00282   if (userList_) {
00283     userList_->clear();
00284 
00285     SimpleChatServer::UserSet users = server_.users();
00286 
00287     UserMap oldUsers = users_;
00288     users_.clear();
00289 
00290     for (SimpleChatServer::UserSet::iterator i = users.begin();
00291          i != users.end(); ++i) {
00292       WCheckBox *w = new WCheckBox(escapeText(*i), userList_);
00293       w->setInline(false);
00294 
00295       UserMap::const_iterator j = oldUsers.find(*i);
00296       if (j != oldUsers.end())
00297         w->setChecked(j->second);
00298       else
00299         w->setChecked(true);
00300 
00301       users_[*i] = w->isChecked();
00302       w->changed().connect(this, &SimpleChatWidget::updateUser);
00303 
00304       if (*i == user_)
00305         w->setStyleClass("chat-self");
00306     }
00307   }
00308 }
00309 
00310 void SimpleChatWidget::newMessage()
00311 { }
00312 
00313 void SimpleChatWidget::updateUser()
00314 {
00315   WCheckBox *b = dynamic_cast<WCheckBox *>(sender());
00316   users_[b->text()] = b->isChecked();
00317 }
00318 
00319 void SimpleChatWidget::processChatEvent(const ChatEvent& event)
00320 {
00321   WApplication *app = WApplication::instance();
00322 
00323   /*
00324    * This is where the "server-push" happens. The chat server posts to this
00325    * event from other sessions, see SimpleChatServer::postChatEvent()
00326    */
00327 
00328   /*
00329    * Format and append the line to the conversation.
00330    *
00331    * This is also the step where the automatic XSS filtering will kick in:
00332    * - if another user tried to pass on some JavaScript, it is filtered away.
00333    * - if another user did not provide valid XHTML, the text is automatically
00334    *   interpreted as PlainText
00335    */
00336 
00337   /*
00338    * If it is not a plain message, also update the user list.
00339    */
00340   if (event.type() != ChatEvent::Message) {
00341     if (event.type() == ChatEvent::Rename && event.user() == user_)
00342       user_ = event.data();
00343 
00344     updateUsers();
00345   }
00346 
00347   newMessage();
00348 
00349   /*
00350    * Anything else doesn't matter if we are not logged in.
00351    */
00352   if (!loggedIn()) {
00353     app->triggerUpdate();
00354     return;
00355   }
00356 
00357   bool display = event.type() != ChatEvent::Message
00358     || !userList_
00359     || (users_.find(event.user()) != users_.end() && users_[event.user()]);
00360 
00361   if (display) {
00362     WText *w = new WText(messages_);
00363 
00364     /*
00365      * If it fails, it is because the content wasn't valid XHTML
00366      */
00367     if (!w->setText(event.formattedHTML(user_, XHTMLText))) {
00368       w->setText(event.formattedHTML(user_, PlainText));
00369       w->setTextFormat(XHTMLText);
00370     }
00371 
00372     w->setInline(false);
00373     w->setStyleClass("chat-msg");
00374 
00375     /*
00376      * Leave no more than 100 messages in the back-log
00377      */
00378     if (messages_->count() > 100)
00379       delete messages_->children()[0];
00380 
00381     /*
00382      * Little javascript trick to make sure we scroll along with new content
00383      */
00384     app->doJavaScript(messages_->jsRef() + ".scrollTop += "
00385                        + messages_->jsRef() + ".scrollHeight;");
00386 
00387     /* If this message belongs to another user, play a received sound */
00388     if (event.user() != user_ && messageReceived_)
00389       messageReceived_->play();
00390   }
00391 
00392   /*
00393    * This is the server push action: we propagate the updated UI to the client,
00394    * (when the event was triggered by another user)
00395    */
00396   app->triggerUpdate();
00397 }

Generated on Fri Jul 27 2012 for the C++ Web Toolkit (Wt) by doxygen 1.7.5.1