Wt examples 4.5.0
SimpleChatWidget.C
Go to the documentation of this file.
1/*
2 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3 *
4 * See the LICENSE file for terms of use.
5 */
6
7#include "SimpleChatWidget.h"
8#include "SimpleChatServer.h"
9
10#include <Wt/WApplication.h>
11#include <Wt/WContainerWidget.h>
12#include <Wt/WEnvironment.h>
13#include <Wt/WInPlaceEdit.h>
14#include <Wt/WHBoxLayout.h>
15#include <Wt/WVBoxLayout.h>
16#include <Wt/WLabel.h>
17#include <Wt/WLineEdit.h>
18#include <Wt/WTemplate.h>
19#include <Wt/WText.h>
20#include <Wt/WTextArea.h>
21#include <Wt/WPushButton.h>
22#include <Wt/WCheckBox.h>
23
24#include <iostream>
25
27 : WContainerWidget(),
28 server_(server),
29 loggedIn_(false),
30 userList_(0),
31 messageReceived_(nullptr)
32{
34 letLogin();
35}
36
38{
39 messageReceived_.reset();
40 logout();
41}
42
44{
46 (this, std::bind(&SimpleChatWidget::processChatEvent, this, std::placeholders::_1)))
47 Wt::WApplication::instance()->enableUpdates(true);
48}
49
51{
52 if (server_.disconnect(this))
53 Wt::WApplication::instance()->enableUpdates(false);
54}
55
57{
58 clear();
59
60 auto vLayout = setLayout(std::make_unique<Wt::WVBoxLayout>());
61
62 auto hLayout_(std::make_unique<Wt::WHBoxLayout>());
63 auto hLayout = hLayout_.get();
64 vLayout->addLayout(std::move(hLayout_), 0,
65 Wt::AlignmentFlag::Top | Wt::AlignmentFlag::Left);
66
67 hLayout->addWidget(std::make_unique<Wt::WLabel>("User name:"),
68 0, Wt::AlignmentFlag::Middle);
69
70 userNameEdit_ = hLayout->addWidget(std::make_unique<Wt::WLineEdit>(user_),
71 0, Wt::AlignmentFlag::Middle);
72 userNameEdit_->setFocus();
73
74 auto button = hLayout->addWidget(std::make_unique<Wt::WPushButton>("Login"),
75 0, Wt::AlignmentFlag::Middle);
76
77 button->clicked().connect(this, &SimpleChatWidget::login);
78 userNameEdit_->enterPressed().connect(this, &SimpleChatWidget::login);
79
80 statusMsg_ = vLayout->addWidget(std::make_unique<Wt::WText>());
81 statusMsg_->setTextFormat(Wt::TextFormat::Plain);
82}
83
85{
86 if (!loggedIn()) {
87 Wt::WString name = userNameEdit_->text();
88
90 messageReceived_ = std::make_unique<Wt::WSound>("sounds/message_received.mp3");
91
92 if (!startChat(name))
93 statusMsg_->setText("Sorry, name '" + escapeText(name) +
94 "' is already taken.");
95 }
96}
97
99{
100 if (loggedIn()) {
101 loggedIn_ = false;
103 disconnect();
104
105 letLogin();
106 }
107}
108
109void SimpleChatWidget::createLayout(std::unique_ptr<WWidget> messages, std::unique_ptr<WWidget> userList,
110 std::unique_ptr<WWidget> messageEdit,
111 std::unique_ptr<WWidget> sendButton, std::unique_ptr<WWidget> logoutButton)
112{
113 /*
114 * Create a vertical layout, which will hold 3 rows,
115 * organized like this:
116 *
117 * WVBoxLayout
118 * --------------------------------------------
119 * | nested WHBoxLayout (vertical stretch=1) |
120 * | | |
121 * | messages | userList |
122 * | (horizontal stretch=1) | |
123 * | | |
124 * --------------------------------------------
125 * | message edit area |
126 * --------------------------------------------
127 * | WHBoxLayout |
128 * | send | logout |
129 * --------------------------------------------
130 */
131 auto vLayout = std::make_unique<Wt::WVBoxLayout>();
132
133 // Create a horizontal layout for the messages | userslist.
134 auto hLayout = std::make_unique<Wt::WHBoxLayout>();
135
136 // Choose JavaScript implementation explicitly to avoid log warning (needed for resizable layout)
137 hLayout->setPreferredImplementation(Wt::LayoutImplementation::JavaScript);
138
139 // Add widget to horizontal layout with stretch = 1
140 messages->setStyleClass("chat-msgs");
141 hLayout->addWidget(std::move(messages), 1);
142
143 // Add another widget to horizontal layout with stretch = 0
144 userList->setStyleClass("chat-users");
145 hLayout->addWidget(std::move(userList));
146
147 hLayout->setResizable(0, true);
148
149 // Add nested layout to vertical layout with stretch = 1
150 vLayout->addLayout(std::move(hLayout), 1);
151
152 // Add widget to vertical layout with stretch = 0
153 messageEdit->setStyleClass("chat-noedit");
154 vLayout->addWidget(std::move(messageEdit));
155
156 // Create a horizontal layout for the buttons.
157 hLayout = std::make_unique<Wt::WHBoxLayout>();
158
159 // Add button to horizontal layout with stretch = 0
160 hLayout->addWidget(std::move(sendButton));
161
162 // Add button to horizontal layout with stretch = 0
163 hLayout->addWidget(std::move(logoutButton));
164
165 // Add nested layout to vertical layout with stretch = 0
166 vLayout->addLayout(std::move(hLayout), 0, Wt::AlignmentFlag::Left);
167
168 this->setLayout(std::move(vLayout));
169}
170
172{
173 return loggedIn_;
174}
175
176void SimpleChatWidget::render(Wt::WFlags<Wt::RenderFlag> flags)
177{
178 if (flags.test(Wt::RenderFlag::Full)) {
179 if (loggedIn()) {
180 /* Handle a page refresh correctly */
181 messageEdit_->setText(Wt::WString::Empty);
182 doJavaScript("setTimeout(function() { "
183 + messages_->jsRef() + ".scrollTop += "
184 + messages_->jsRef() + ".scrollHeight;}, 0);");
185 }
186 }
187
188 WContainerWidget::render(flags);
189}
190
191bool SimpleChatWidget::startChat(const Wt::WString& user)
192{
193 /*
194 * When logging in, we pass our processChatEvent method as the function that
195 * is used to indicate a new chat event for this user.
196 */
197 if (server_.login(user)) {
198 loggedIn_ = true;
199 connect();
200
201 user_ = user;
202
203 clear();
204 userNameEdit_ = 0;
205
206 auto messagesPtr = std::make_unique<WContainerWidget>();
207 auto userListPtr = std::make_unique<WContainerWidget>();
208 auto messageEditPtr = std::make_unique<Wt::WTextArea>();
209 auto sendButtonPtr = std::make_unique<Wt::WPushButton>("Send");
210 auto logoutButtonPtr = std::make_unique<Wt::WPushButton>("Logout");
211
212 messages_ = messagesPtr.get();
213 userList_ = userListPtr.get();
214 messageEdit_ = messageEditPtr.get();
215 sendButton_ = sendButtonPtr.get();
216 Wt::Core::observing_ptr<Wt::WPushButton> logoutButton = logoutButtonPtr.get();
217
218 messageEdit_->setRows(2);
219 messageEdit_->setFocus();
220
221 // Display scroll bars if contents overflows
222 messages_->setOverflow(Wt::Overflow::Auto);
223 userList_->setOverflow(Wt::Overflow::Auto);
224
225 createLayout(std::move(messagesPtr), std::move(userListPtr),
226 std::move(messageEditPtr),
227 std::move(sendButtonPtr), std::move(logoutButtonPtr));
228
229 /*
230 * Connect event handlers:
231 * - click on button
232 * - enter in text area
233 *
234 * We will clear the input field using a small custom client-side
235 * JavaScript invocation.
236 */
237
238 // Create a JavaScript 'slot' (JSlot). The JavaScript slot always takes
239 // 2 arguments: the originator of the event (in our case the
240 // button or text area), and the JavaScript event object.
241 clearInput_.setJavaScript
242 ("function(o, e) { setTimeout(function() {"
243 "" + messageEdit_->jsRef() + ".value='';"
244 "}, 0); }");
245
246 /*
247 * Set the connection monitor
248 *
249 * The connection monitor is a javascript monitor that will
250 * nootify the given object by calling the onChange method to
251 * inform of connection change (use of websockets, connection
252 * online/offline) Here we just disable the TextEdit when we are
253 * offline and enable it once we're back online
254 */
255 Wt::WApplication::instance()->setConnectionMonitor(
256 "window.monitor={ "
257 "'onChange':function(type, newV) {"
258 "var connected = window.monitor.status.connectionStatus != 0;"
259 "if(connected) {"
260 + messageEdit_->jsRef() + ".disabled=false;"
261 + messageEdit_->jsRef() + ".placeholder='';"
262 "} else { "
263 + messageEdit_->jsRef() + ".disabled=true;"
264 + messageEdit_->jsRef() + ".placeholder='connection lost';"
265 "}"
266 "}"
267 "}"
268 );
269
270 // Bind the C++ and JavaScript event handlers.
271 if (sendButton_) {
272 sendButton_->clicked().connect(this, &SimpleChatWidget::send);
273 sendButton_->clicked().connect(clearInput_);
274 sendButton_->clicked().connect((WWidget *)messageEdit_,
275 &WWidget::setFocus);
276 }
277 messageEdit_->enterPressed().connect(this, &SimpleChatWidget::send);
278 messageEdit_->enterPressed().connect(clearInput_);
279 messageEdit_->enterPressed().connect((WWidget *)messageEdit_,
280 &WWidget::setFocus);
281
282 // Prevent the enter from generating a new line, which is its default
283 // action
284 messageEdit_->enterPressed().preventDefaultAction();
285
286 if (logoutButton)
287 logoutButton->clicked().connect(this, &SimpleChatWidget::logout);
288
289 auto nameEdit = std::make_unique<Wt::WInPlaceEdit>();
290 nameEdit->addStyleClass("name-edit");
291 nameEdit->setButtonsEnabled(false);
292 nameEdit->setText(user_);
293 nameEdit->valueChanged().connect(this, &SimpleChatWidget::changeName);
294
295 Wt::WTemplate *joinMsg = messages_->addWidget(std::make_unique<Wt::WTemplate>(tr("join-msg.template")));
296 joinMsg->bindWidget("name", std::move(nameEdit));
297 joinMsg->setStyleClass("chat-msg");
298
299 updateUsers();
300
301 return true;
302 } else
303 return false;
304}
305
306void SimpleChatWidget::changeName(const Wt::WString& name)
307{
308 if (!name.empty()) {
309 if (server_.changeName(user_, name))
310 user_ = name;
311 }
312}
313
315{
316 if (!messageEdit_->text().empty())
318}
319
321{
322 if (userList_) {
323 userList_->clear();
324
326
327 UserMap oldUsers = users_;
328 users_.clear();
329
330 for (SimpleChatServer::UserSet::iterator i = users.begin();
331 i != users.end(); ++i) {
332 Wt::WCheckBox *w = userList_->addWidget(std::make_unique<Wt::WCheckBox>(escapeText(*i)));
333 w->setInline(false);
334
335 UserMap::const_iterator j = oldUsers.find(*i);
336 if (j != oldUsers.end())
337 w->setChecked(j->second);
338 else
339 w->setChecked(true);
340
341 users_[*i] = w->isChecked();
342 w->changed().connect(std::bind(&SimpleChatWidget::updateUser, this, w));
343
344 if (*i == user_)
345 w->setStyleClass("chat-self");
346 }
347 }
348}
349
351{ }
352
353void SimpleChatWidget::updateUser(Wt::WCheckBox *b)
354{
355 users_[b->text()] = b->isChecked();
356}
357
359{
360 Wt::WApplication *app = Wt::WApplication::instance();
361
362 /*
363 * This is where the "server-push" happens. The chat server posts to this
364 * event from other sessions, see SimpleChatServer::postChatEvent()
365 */
366
367 /*
368 * Format and append the line to the conversation.
369 *
370 * This is also the step where the automatic XSS filtering will kick in:
371 * - if another user tried to pass on some JavaScript, it is filtered away.
372 * - if another user did not provide valid XHTML, the text is automatically
373 * interpreted as PlainText
374 */
375
376 /*
377 * If it is not a plain message, also update the user list.
378 */
379 if (event.type() != ChatEvent::Message) {
380 if (event.type() == ChatEvent::Rename && event.user() == user_)
381 user_ = event.data();
382
383 updateUsers();
384 }
385
386 /*
387 * This is the server call: we (schedule to) propagate the updated UI to
388 * the client.
389 *
390 * This schedules an update and returns immediately
391 */
392 app->triggerUpdate();
393
394 newMessage();
395
396 /*
397 * Anything else doesn't matter if we are not logged in.
398 */
399 if (!loggedIn())
400 return;
401
402 bool display = event.type() != ChatEvent::Message
403 || !userList_
404 || (users_.find(event.user()) != users_.end() && users_[event.user()]);
405
406 if (display) {
407 Wt::WText *w = messages_->addWidget(std::make_unique<Wt::WText>());
408
409 /*
410 * If it fails, it is because the content wasn't valid XHTML
411 */
412 if (!w->setText(event.formattedHTML(user_, Wt::TextFormat::XHTML))) {
413 w->setText(event.formattedHTML(user_, Wt::TextFormat::Plain));
414 w->setTextFormat(Wt::TextFormat::XHTML);
415 }
416
417 w->setInline(false);
418 w->setStyleClass("chat-msg");
419
420 /*
421 * Leave no more than 100 messages in the back-log
422 */
423 if (messages_->count() > 100)
424 messages_->removeChild(messages_->children()[0]);
425
426 /*
427 * Little javascript trick to make sure we scroll along with new content
428 */
429 app->doJavaScript(messages_->jsRef() + ".scrollTop += "
430 + messages_->jsRef() + ".scrollHeight;");
431
432 /* If this message belongs to another user, play a received sound */
433 if (event.user() != user_ && messageReceived_)
434 messageReceived_->play();
435 }
436}
Encapsulate a chat event.
const Wt::WString formattedHTML(const Wt::WString &user, Wt::TextFormat format) const
Get the message formatted as HTML, rendered for the given user.
Type type() const
Get the event type.
const Wt::WString & user() const
Get the user who caused the event.
A simple chat server.
bool changeName(const Wt::WString &user, const Wt::WString &newUser)
Changes the name.
bool disconnect(Client *client)
Disconnect from the chat server.
UserSet users()
Get the users currently logged in.
void logout(const Wt::WString &user)
Logout from the server.
std::set< Wt::WString > UserSet
Typedef for a collection of user names.
bool connect(Client *client, const ChatEventCallback &handleEvent)
Connects to the chat server.
void sendMessage(const Wt::WString &user, const Wt::WString &message)
Send a message on behalve of a user.
Wt::WString suggestGuest()
Get a suggestion for a guest user name.
bool login(const Wt::WString &user)
Try to login with given user name.
bool startChat(const Wt::WString &user)
Start a chat for the given user.
virtual void updateUsers()
void updateUser(Wt::WCheckBox *b)
Wt::WContainerWidget * messages_
Wt::WTextArea * messageEdit_
Wt::WLineEdit * userNameEdit_
SimpleChatWidget(SimpleChatServer &server)
Create a chat widget that will connect to the given server.
void letLogin()
Show a simple login screen.
std::unique_ptr< Wt::WSound > messageReceived_
bool loggedIn() const
Wt::WText * statusMsg_
virtual void createLayout(std::unique_ptr< Wt::WWidget > messages, std::unique_ptr< Wt::WWidget > userList, std::unique_ptr< Wt::WWidget > messageEdit, std::unique_ptr< Wt::WWidget > sendButton, std::unique_ptr< Wt::WWidget > logoutButton)
virtual void render(Wt::WFlags< Wt::RenderFlag > flags)
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_
SimpleChatServer & server_
virtual void newMessage()
Wt::Core::observing_ptr< Wt::WPushButton > sendButton_
void changeName(const Wt::WString &name)
void processChatEvent(const ChatEvent &event)
std::map< Wt::WString, bool > UserMap
~SimpleChatWidget()
Delete a chat widget.

Generated on Fri Sep 10 2021 for the C++ Web Toolkit (Wt) by doxygen 1.9.2