Wt examples: /home/koen/project/wt/public-git/wt/examples/simplechat/SimpleChatWidget.C Source File

Wt examples 3.3.0

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

/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 }