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