Wt examples 3.1.10
|
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 }