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

Generated on Tue Nov 29 2011 for the C++ Web Toolkit (Wt) by doxygen 1.7.5.1