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

Generated on Wed Jul 27 2011 for the C++ Web Toolkit (Wt) by doxygen 1.7.4