Wt examples  3.3.5
/usr/src/RPM/BUILD/wt-3.3.5-rc2/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/WInPlaceEdit>
00014 #include <Wt/WHBoxLayout>
00015 #include <Wt/WVBoxLayout>
00016 #include <Wt/WLabel>
00017 #include <Wt/WLineEdit>
00018 #include <Wt/WTemplate>
00019 #include <Wt/WText>
00020 #include <Wt/WTextArea>
00021 #include <Wt/WPushButton>
00022 #include <Wt/WCheckBox>
00023 
00024 #include <iostream>
00025 
00026 using namespace Wt;
00027 
00028 SimpleChatWidget::SimpleChatWidget(SimpleChatServer& server,
00029                                    Wt::WContainerWidget *parent)
00030   : WContainerWidget(parent),
00031     server_(server),
00032     loggedIn_(false),
00033     userList_(0),
00034     messageReceived_(0)
00035 {
00036   user_ = server_.suggestGuest();
00037   letLogin();
00038 }
00039 
00040 SimpleChatWidget::~SimpleChatWidget()
00041 {
00042   delete messageReceived_;
00043   logout();
00044 }
00045 
00046 void SimpleChatWidget::connect()
00047 {
00048   if (server_.connect
00049       (this, boost::bind(&SimpleChatWidget::processChatEvent, this, _1)))
00050     Wt::WApplication::instance()->enableUpdates(true);
00051 }
00052 
00053 void SimpleChatWidget::disconnect()
00054 {
00055   if (server_.disconnect(this))
00056     Wt::WApplication::instance()->enableUpdates(false);
00057 }
00058 
00059 void SimpleChatWidget::letLogin()
00060 {
00061   clear();
00062 
00063   WVBoxLayout *vLayout = new WVBoxLayout();
00064   setLayout(vLayout);
00065 
00066   WHBoxLayout *hLayout = new WHBoxLayout();
00067   vLayout->addLayout(hLayout, 0, AlignTop | AlignLeft);
00068 
00069   hLayout->addWidget(new WLabel("User name:"), 0, AlignMiddle);
00070   hLayout->addWidget(userNameEdit_ = new WLineEdit(user_), 0, AlignMiddle);
00071   userNameEdit_->setFocus();
00072 
00073   WPushButton *b = new WPushButton("Login");
00074   hLayout->addWidget(b, 0, AlignMiddle);
00075 
00076   b->clicked().connect(this, &SimpleChatWidget::login);
00077   userNameEdit_->enterPressed().connect(this, &SimpleChatWidget::login);
00078 
00079   vLayout->addWidget(statusMsg_ = new WText());
00080   statusMsg_->setTextFormat(PlainText);
00081 }
00082 
00083 void SimpleChatWidget::login()
00084 {
00085   if (!loggedIn()) {
00086     WString name = userNameEdit_->text();
00087 
00088     if (!messageReceived_)
00089       messageReceived_ = new WSound("sounds/message_received.mp3");
00090 
00091     if (!startChat(name))
00092       statusMsg_->setText("Sorry, name '" + escapeText(name) +
00093                           "' is already taken.");
00094   }
00095 }
00096 
00097 void SimpleChatWidget::logout()
00098 {
00099   if (loggedIn()) {
00100     loggedIn_ = false;
00101     server_.logout(user_);
00102     disconnect();
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     /*
00219      * Connect event handlers:
00220      *  - click on button
00221      *  - enter in text area
00222      *
00223      * We will clear the input field using a small custom client-side
00224      * JavaScript invocation.
00225      */
00226 
00227     // Create a JavaScript 'slot' (JSlot). The JavaScript slot always takes
00228     // 2 arguments: the originator of the event (in our case the
00229     // button or text area), and the JavaScript event object.
00230     clearInput_.setJavaScript
00231       ("function(o, e) { setTimeout(function() {"
00232        "" + messageEdit_->jsRef() + ".value='';"
00233        "}, 0); }");
00234 
00235     /*
00236      * Set the connection monitor
00237      *
00238      * The connection monitor is a javascript monitor that will
00239      * nootify the given object by calling the onChange method to
00240      * inform of connection change (use of websockets, connection
00241      * online/offline) Here we just disable the TextEdit when we are
00242      * offline and enable it once we're back online
00243      */
00244     WApplication::instance()->setConnectionMonitor(
00245                 "window.monitor={ "
00246                 "'onChange':function(type, newV) {"
00247                   "var connected = window.monitor.status.connectionStatus != 0;"
00248                   "if(connected) {"
00249                         + messageEdit_->jsRef() + ".disabled=false;"
00250                         + messageEdit_->jsRef() + ".placeholder='';"
00251                   "} else { "
00252                         + messageEdit_->jsRef() + ".disabled=true;"
00253                         + messageEdit_->jsRef() + ".placeholder='connection lost';"
00254                   "}"
00255                 "}"
00256                 "}"
00257                 );
00258 
00259     // Bind the C++ and JavaScript event handlers.
00260     sendButton_->clicked().connect(this, &SimpleChatWidget::send);
00261     messageEdit_->enterPressed().connect(this, &SimpleChatWidget::send);
00262     sendButton_->clicked().connect(clearInput_);
00263     messageEdit_->enterPressed().connect(clearInput_);
00264     sendButton_->clicked().connect((WWidget *)messageEdit_,
00265                                    &WWidget::setFocus);
00266     messageEdit_->enterPressed().connect((WWidget *)messageEdit_,
00267                                          &WWidget::setFocus);
00268 
00269     // Prevent the enter from generating a new line, which is its default
00270     // action
00271     messageEdit_->enterPressed().preventDefaultAction();
00272 
00273     logoutButton->clicked().connect(this, &SimpleChatWidget::logout);
00274 
00275     WInPlaceEdit *nameEdit = new WInPlaceEdit();
00276     nameEdit->addStyleClass("name-edit");
00277     nameEdit->setButtonsEnabled(false);
00278     nameEdit->setText(user_);
00279     nameEdit->valueChanged().connect(this, &SimpleChatWidget::changeName);
00280 
00281     WTemplate *joinMsg = new WTemplate(tr("join-msg.template"), messages_);
00282     joinMsg->bindWidget("name", nameEdit);
00283     joinMsg->setStyleClass("chat-msg");
00284 
00285     if (!userList_->parent()) {
00286       delete userList_;
00287       userList_ = 0;
00288     }
00289 
00290     if (!sendButton_->parent()) {
00291       delete sendButton_;
00292       sendButton_ = 0;
00293     }
00294 
00295     if (!logoutButton->parent())
00296       delete logoutButton;
00297 
00298     updateUsers();
00299     
00300     return true;
00301   } else
00302     return false;
00303 }
00304 
00305 void SimpleChatWidget::changeName(const WString& name)
00306 {
00307   if (!name.empty()) {
00308     if (server_.changeName(user_, name))
00309       user_ = name;
00310   }
00311 }
00312 
00313 void SimpleChatWidget::send()
00314 {
00315   if (!messageEdit_->text().empty())
00316     server_.sendMessage(user_, messageEdit_->text());
00317 }
00318 
00319 void SimpleChatWidget::updateUsers()
00320 {
00321   if (userList_) {
00322     userList_->clear();
00323 
00324     SimpleChatServer::UserSet users = server_.users();
00325 
00326     UserMap oldUsers = users_;
00327     users_.clear();
00328 
00329     for (SimpleChatServer::UserSet::iterator i = users.begin();
00330          i != users.end(); ++i) {
00331       WCheckBox *w = new WCheckBox(escapeText(*i), userList_);
00332       w->setInline(false);
00333 
00334       UserMap::const_iterator j = oldUsers.find(*i);
00335       if (j != oldUsers.end())
00336         w->setChecked(j->second);
00337       else
00338         w->setChecked(true);
00339 
00340       users_[*i] = w->isChecked();
00341       w->changed().connect(this, &SimpleChatWidget::updateUser);
00342 
00343       if (*i == user_)
00344         w->setStyleClass("chat-self");
00345     }
00346   }
00347 }
00348 
00349 void SimpleChatWidget::newMessage()
00350 { }
00351 
00352 void SimpleChatWidget::updateUser()
00353 {
00354   WCheckBox *b = dynamic_cast<WCheckBox *>(sender());
00355   users_[b->text()] = b->isChecked();
00356 }
00357 
00358 void SimpleChatWidget::processChatEvent(const ChatEvent& event)
00359 {
00360   WApplication *app = WApplication::instance();
00361 
00362   /*
00363    * This is where the "server-push" happens. The chat server posts to this
00364    * event from other sessions, see SimpleChatServer::postChatEvent()
00365    */
00366 
00367   /*
00368    * Format and append the line to the conversation.
00369    *
00370    * This is also the step where the automatic XSS filtering will kick in:
00371    * - if another user tried to pass on some JavaScript, it is filtered away.
00372    * - if another user did not provide valid XHTML, the text is automatically
00373    *   interpreted as PlainText
00374    */
00375 
00376   /*
00377    * If it is not a plain message, also update the user list.
00378    */
00379   if (event.type() != ChatEvent::Message) {
00380     if (event.type() == ChatEvent::Rename && event.user() == user_)
00381       user_ = event.data();
00382 
00383     updateUsers();
00384   }
00385 
00386   /*
00387    * This is the server call: we (schedule to) propagate the updated UI to
00388    * the client.
00389    *
00390    * This schedules an update and returns immediately
00391    */
00392   app->triggerUpdate();
00393 
00394   newMessage();
00395 
00396   /*
00397    * Anything else doesn't matter if we are not logged in.
00398    */
00399   if (!loggedIn())
00400     return;
00401 
00402   bool display = event.type() != ChatEvent::Message
00403     || !userList_
00404     || (users_.find(event.user()) != users_.end() && users_[event.user()]);
00405 
00406   if (display) {
00407     WText *w = new WText(messages_);
00408 
00409     /*
00410      * If it fails, it is because the content wasn't valid XHTML
00411      */
00412     if (!w->setText(event.formattedHTML(user_, XHTMLText))) {
00413       w->setText(event.formattedHTML(user_, PlainText));
00414       w->setTextFormat(XHTMLText);
00415     }
00416 
00417     w->setInline(false);
00418     w->setStyleClass("chat-msg");
00419 
00420     /*
00421      * Leave no more than 100 messages in the back-log
00422      */
00423     if (messages_->count() > 100)
00424       delete messages_->children()[0];
00425 
00426     /*
00427      * Little javascript trick to make sure we scroll along with new content
00428      */
00429     app->doJavaScript(messages_->jsRef() + ".scrollTop += "
00430                        + messages_->jsRef() + ".scrollHeight;");
00431 
00432     /* If this message belongs to another user, play a received sound */
00433     if (event.user() != user_ && messageReceived_)
00434       messageReceived_->play();
00435   }
00436 }

Generated on Tue Mar 22 2016 for the C++ Web Toolkit (Wt) by doxygen 1.7.6.1