|
Wt examples
3.3.5
|
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/WInPlaceEdit> 00014 #include <Wt/WHBoxLayout> 00015 #include <Wt/WVBoxLayout> 00016 #include <Wt/WLabel> 00017 #include <Wt/WLineEdit> 00018 #include <Wt/WTemplate> 00019 #include <Wt/WText> 00020 #include <Wt/WTextArea> 00021 #include <Wt/WPushButton> 00022 #include <Wt/WCheckBox> 00023 00024 #include <iostream> 00025 00026 using namespace Wt; 00027 00028 SimpleChatWidget::SimpleChatWidget(SimpleChatServer& server, 00029 Wt::WContainerWidget *parent) 00030 : WContainerWidget(parent), 00031 server_(server), 00032 loggedIn_(false), 00033 userList_(0), 00034 messageReceived_(0) 00035 { 00036 user_ = server_.suggestGuest(); 00037 letLogin(); 00038 } 00039 00040 SimpleChatWidget::~SimpleChatWidget() 00041 { 00042 delete messageReceived_; 00043 logout(); 00044 } 00045 00046 void SimpleChatWidget::connect() 00047 { 00048 if (server_.connect 00049 (this, boost::bind(&SimpleChatWidget::processChatEvent, this, _1))) 00050 Wt::WApplication::instance()->enableUpdates(true); 00051 } 00052 00053 void SimpleChatWidget::disconnect() 00054 { 00055 if (server_.disconnect(this)) 00056 Wt::WApplication::instance()->enableUpdates(false); 00057 } 00058 00059 void SimpleChatWidget::letLogin() 00060 { 00061 clear(); 00062 00063 WVBoxLayout *vLayout = new WVBoxLayout(); 00064 setLayout(vLayout); 00065 00066 WHBoxLayout *hLayout = new WHBoxLayout(); 00067 vLayout->addLayout(hLayout, 0, AlignTop | AlignLeft); 00068 00069 hLayout->addWidget(new WLabel("User name:"), 0, AlignMiddle); 00070 hLayout->addWidget(userNameEdit_ = new WLineEdit(user_), 0, AlignMiddle); 00071 userNameEdit_->setFocus(); 00072 00073 WPushButton *b = new WPushButton("Login"); 00074 hLayout->addWidget(b, 0, AlignMiddle); 00075 00076 b->clicked().connect(this, &SimpleChatWidget::login); 00077 userNameEdit_->enterPressed().connect(this, &SimpleChatWidget::login); 00078 00079 vLayout->addWidget(statusMsg_ = new WText()); 00080 statusMsg_->setTextFormat(PlainText); 00081 } 00082 00083 void SimpleChatWidget::login() 00084 { 00085 if (!loggedIn()) { 00086 WString name = userNameEdit_->text(); 00087 00088 if (!messageReceived_) 00089 messageReceived_ = new WSound("sounds/message_received.mp3"); 00090 00091 if (!startChat(name)) 00092 statusMsg_->setText("Sorry, name '" + escapeText(name) + 00093 "' is already taken."); 00094 } 00095 } 00096 00097 void SimpleChatWidget::logout() 00098 { 00099 if (loggedIn()) { 00100 loggedIn_ = false; 00101 server_.logout(user_); 00102 disconnect(); 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 /* 00219 * Connect event handlers: 00220 * - click on button 00221 * - enter in text area 00222 * 00223 * We will clear the input field using a small custom client-side 00224 * JavaScript invocation. 00225 */ 00226 00227 // Create a JavaScript 'slot' (JSlot). The JavaScript slot always takes 00228 // 2 arguments: the originator of the event (in our case the 00229 // button or text area), and the JavaScript event object. 00230 clearInput_.setJavaScript 00231 ("function(o, e) { setTimeout(function() {" 00232 "" + messageEdit_->jsRef() + ".value='';" 00233 "}, 0); }"); 00234 00235 /* 00236 * Set the connection monitor 00237 * 00238 * The connection monitor is a javascript monitor that will 00239 * nootify the given object by calling the onChange method to 00240 * inform of connection change (use of websockets, connection 00241 * online/offline) Here we just disable the TextEdit when we are 00242 * offline and enable it once we're back online 00243 */ 00244 WApplication::instance()->setConnectionMonitor( 00245 "window.monitor={ " 00246 "'onChange':function(type, newV) {" 00247 "var connected = window.monitor.status.connectionStatus != 0;" 00248 "if(connected) {" 00249 + messageEdit_->jsRef() + ".disabled=false;" 00250 + messageEdit_->jsRef() + ".placeholder='';" 00251 "} else { " 00252 + messageEdit_->jsRef() + ".disabled=true;" 00253 + messageEdit_->jsRef() + ".placeholder='connection lost';" 00254 "}" 00255 "}" 00256 "}" 00257 ); 00258 00259 // Bind the C++ and JavaScript event handlers. 00260 sendButton_->clicked().connect(this, &SimpleChatWidget::send); 00261 messageEdit_->enterPressed().connect(this, &SimpleChatWidget::send); 00262 sendButton_->clicked().connect(clearInput_); 00263 messageEdit_->enterPressed().connect(clearInput_); 00264 sendButton_->clicked().connect((WWidget *)messageEdit_, 00265 &WWidget::setFocus); 00266 messageEdit_->enterPressed().connect((WWidget *)messageEdit_, 00267 &WWidget::setFocus); 00268 00269 // Prevent the enter from generating a new line, which is its default 00270 // action 00271 messageEdit_->enterPressed().preventDefaultAction(); 00272 00273 logoutButton->clicked().connect(this, &SimpleChatWidget::logout); 00274 00275 WInPlaceEdit *nameEdit = new WInPlaceEdit(); 00276 nameEdit->addStyleClass("name-edit"); 00277 nameEdit->setButtonsEnabled(false); 00278 nameEdit->setText(user_); 00279 nameEdit->valueChanged().connect(this, &SimpleChatWidget::changeName); 00280 00281 WTemplate *joinMsg = new WTemplate(tr("join-msg.template"), messages_); 00282 joinMsg->bindWidget("name", nameEdit); 00283 joinMsg->setStyleClass("chat-msg"); 00284 00285 if (!userList_->parent()) { 00286 delete userList_; 00287 userList_ = 0; 00288 } 00289 00290 if (!sendButton_->parent()) { 00291 delete sendButton_; 00292 sendButton_ = 0; 00293 } 00294 00295 if (!logoutButton->parent()) 00296 delete logoutButton; 00297 00298 updateUsers(); 00299 00300 return true; 00301 } else 00302 return false; 00303 } 00304 00305 void SimpleChatWidget::changeName(const WString& name) 00306 { 00307 if (!name.empty()) { 00308 if (server_.changeName(user_, name)) 00309 user_ = name; 00310 } 00311 } 00312 00313 void SimpleChatWidget::send() 00314 { 00315 if (!messageEdit_->text().empty()) 00316 server_.sendMessage(user_, messageEdit_->text()); 00317 } 00318 00319 void SimpleChatWidget::updateUsers() 00320 { 00321 if (userList_) { 00322 userList_->clear(); 00323 00324 SimpleChatServer::UserSet users = server_.users(); 00325 00326 UserMap oldUsers = users_; 00327 users_.clear(); 00328 00329 for (SimpleChatServer::UserSet::iterator i = users.begin(); 00330 i != users.end(); ++i) { 00331 WCheckBox *w = new WCheckBox(escapeText(*i), userList_); 00332 w->setInline(false); 00333 00334 UserMap::const_iterator j = oldUsers.find(*i); 00335 if (j != oldUsers.end()) 00336 w->setChecked(j->second); 00337 else 00338 w->setChecked(true); 00339 00340 users_[*i] = w->isChecked(); 00341 w->changed().connect(this, &SimpleChatWidget::updateUser); 00342 00343 if (*i == user_) 00344 w->setStyleClass("chat-self"); 00345 } 00346 } 00347 } 00348 00349 void SimpleChatWidget::newMessage() 00350 { } 00351 00352 void SimpleChatWidget::updateUser() 00353 { 00354 WCheckBox *b = dynamic_cast<WCheckBox *>(sender()); 00355 users_[b->text()] = b->isChecked(); 00356 } 00357 00358 void SimpleChatWidget::processChatEvent(const ChatEvent& event) 00359 { 00360 WApplication *app = WApplication::instance(); 00361 00362 /* 00363 * This is where the "server-push" happens. The chat server posts to this 00364 * event from other sessions, see SimpleChatServer::postChatEvent() 00365 */ 00366 00367 /* 00368 * Format and append the line to the conversation. 00369 * 00370 * This is also the step where the automatic XSS filtering will kick in: 00371 * - if another user tried to pass on some JavaScript, it is filtered away. 00372 * - if another user did not provide valid XHTML, the text is automatically 00373 * interpreted as PlainText 00374 */ 00375 00376 /* 00377 * If it is not a plain message, also update the user list. 00378 */ 00379 if (event.type() != ChatEvent::Message) { 00380 if (event.type() == ChatEvent::Rename && event.user() == user_) 00381 user_ = event.data(); 00382 00383 updateUsers(); 00384 } 00385 00386 /* 00387 * This is the server call: we (schedule to) propagate the updated UI to 00388 * the client. 00389 * 00390 * This schedules an update and returns immediately 00391 */ 00392 app->triggerUpdate(); 00393 00394 newMessage(); 00395 00396 /* 00397 * Anything else doesn't matter if we are not logged in. 00398 */ 00399 if (!loggedIn()) 00400 return; 00401 00402 bool display = event.type() != ChatEvent::Message 00403 || !userList_ 00404 || (users_.find(event.user()) != users_.end() && users_[event.user()]); 00405 00406 if (display) { 00407 WText *w = new WText(messages_); 00408 00409 /* 00410 * If it fails, it is because the content wasn't valid XHTML 00411 */ 00412 if (!w->setText(event.formattedHTML(user_, XHTMLText))) { 00413 w->setText(event.formattedHTML(user_, PlainText)); 00414 w->setTextFormat(XHTMLText); 00415 } 00416 00417 w->setInline(false); 00418 w->setStyleClass("chat-msg"); 00419 00420 /* 00421 * Leave no more than 100 messages in the back-log 00422 */ 00423 if (messages_->count() > 100) 00424 delete messages_->children()[0]; 00425 00426 /* 00427 * Little javascript trick to make sure we scroll along with new content 00428 */ 00429 app->doJavaScript(messages_->jsRef() + ".scrollTop += " 00430 + messages_->jsRef() + ".scrollHeight;"); 00431 00432 /* If this message belongs to another user, play a received sound */ 00433 if (event.user() != user_ && messageReceived_) 00434 messageReceived_->play(); 00435 } 00436 }
1.7.6.1