Wt examples  4.10.4
Loading...
Searching...
No Matches
SimpleChatWidget Class Reference

A self-contained chat widget. More...

#include <SimpleChatWidget.h>

Inheritance diagram for SimpleChatWidget:
[legend]

Public Member Functions

 SimpleChatWidget (SimpleChatServer &server)
 Create a chat widget that will connect to the given server.
 
 ~SimpleChatWidget ()
 Delete a chat widget.
 
void connect ()
 
void disconnect ()
 
void letLogin ()
 Show a simple login screen.
 
bool startChat (const Wt::WString &user)
 Start a chat for the given user.
 
void logout ()
 
SimpleChatServerserver ()
 
int userCount ()
 
const Wt::WString & userName () const
 

Protected Member Functions

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 updateUsers ()
 
virtual void newMessage ()
 
virtual void render (Wt::WFlags< Wt::RenderFlag > flags)
 
bool loggedIn () const
 

Private Types

typedef std::map< Wt::WString, bool > UserMap
 

Private Member Functions

void login ()
 
void changeName (const Wt::WString &name)
 
void send ()
 
void updateUser (Wt::WCheckBox *b)
 
void processChatEvent (const ChatEvent &event)
 

Private Attributes

UserMap users_
 
SimpleChatServerserver_
 
bool loggedIn_
 
Wt::JSlot clearInput_
 
Wt::WString user_
 
Wt::WLineEdit * userNameEdit_
 
Wt::WText * statusMsg_
 
Wt::WContainerWidget * messages_
 
Wt::WTextArea * messageEdit_
 
Wt::Core::observing_ptr< Wt::WPushButton > sendButton_
 
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_
 
std::unique_ptr< Wt::WSound > messageReceived_
 

Detailed Description

A self-contained chat widget.

Definition at line 26 of file SimpleChatWidget.h.

Member Typedef Documentation

◆ UserMap

std::map<Wt::WString, bool> SimpleChatWidget::UserMap
private

Definition at line 74 of file SimpleChatWidget.h.

Constructor & Destructor Documentation

◆ SimpleChatWidget()

SimpleChatWidget::SimpleChatWidget ( SimpleChatServer & server)

Create a chat widget that will connect to the given server.

Definition at line 26 of file SimpleChatWidget.C.

27 : WContainerWidget(),
29 loggedIn_(false),
30 userList_(0),
31 messageReceived_(nullptr)
32{
34 letLogin();
35}
Wt::WString suggestGuest()
Get a suggestion for a guest user name.
SimpleChatServer & server()
void letLogin()
Show a simple login screen.
std::unique_ptr< Wt::WSound > messageReceived_
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_
SimpleChatServer & server_

◆ ~SimpleChatWidget()

SimpleChatWidget::~SimpleChatWidget ( )

Delete a chat widget.

Definition at line 37 of file SimpleChatWidget.C.

38{
39 messageReceived_.reset();
40 logout();
41}

Member Function Documentation

◆ changeName()

void SimpleChatWidget::changeName ( const Wt::WString & name)
private

Definition at line 306 of file SimpleChatWidget.C.

307{
308 if (!name.empty()) {
309 if (server_.changeName(user_, name))
310 user_ = name;
311 }
312}
bool changeName(const Wt::WString &user, const Wt::WString &newUser)
Changes the name.

◆ connect()

void SimpleChatWidget::connect ( )

Definition at line 43 of file SimpleChatWidget.C.

44{
46 (this, std::bind(&SimpleChatWidget::processChatEvent, this, std::placeholders::_1)))
47 Wt::WApplication::instance()->enableUpdates(true);
48}
bool connect(Client *client, const ChatEventCallback &handleEvent)
Connects to the chat server.
void processChatEvent(const ChatEvent &event)

◆ createLayout()

void SimpleChatWidget::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 )
protectedvirtual

Definition at line 109 of file SimpleChatWidget.C.

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}

◆ disconnect()

void SimpleChatWidget::disconnect ( )

Definition at line 50 of file SimpleChatWidget.C.

51{
52 if (server_.disconnect(this))
53 Wt::WApplication::instance()->enableUpdates(false);
54}
bool disconnect(Client *client)
Disconnect from the chat server.

◆ letLogin()

void SimpleChatWidget::letLogin ( )

Show a simple login screen.

Definition at line 56 of file SimpleChatWidget.C.

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}
Wt::WLineEdit * userNameEdit_
Wt::WText * statusMsg_

◆ loggedIn()

bool SimpleChatWidget::loggedIn ( ) const
protected

Definition at line 171 of file SimpleChatWidget.C.

172{
173 return loggedIn_;
174}

◆ login()

void SimpleChatWidget::login ( )
private

Definition at line 84 of file SimpleChatWidget.C.

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}
bool startChat(const Wt::WString &user)
Start a chat for the given user.
bool loggedIn() const

◆ logout()

void SimpleChatWidget::logout ( )

Definition at line 98 of file SimpleChatWidget.C.

99{
100 if (loggedIn()) {
101 loggedIn_ = false;
103 disconnect();
104
105 letLogin();
106 }
107}
void logout(const Wt::WString &user)
Logout from the server.

◆ newMessage()

void SimpleChatWidget::newMessage ( )
protectedvirtual

Reimplemented in PopupChatWidget.

Definition at line 350 of file SimpleChatWidget.C.

351{ }

◆ processChatEvent()

void SimpleChatWidget::processChatEvent ( const ChatEvent & event)
private

Definition at line 358 of file SimpleChatWidget.C.

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}
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.
virtual void updateUsers()
Wt::WContainerWidget * messages_
virtual void newMessage()

◆ render()

void SimpleChatWidget::render ( Wt::WFlags< Wt::RenderFlag > flags)
protectedvirtual

Definition at line 176 of file SimpleChatWidget.C.

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}
Wt::WTextArea * messageEdit_

◆ send()

void SimpleChatWidget::send ( )
private

Definition at line 314 of file SimpleChatWidget.C.

315{
316 if (!messageEdit_->text().empty())
318}
void sendMessage(const Wt::WString &user, const Wt::WString &message)
Send a message on behalve of a user.

◆ server()

SimpleChatServer & SimpleChatWidget::server ( )
inline

Definition at line 54 of file SimpleChatWidget.h.

54{ return server_; }

◆ startChat()

bool SimpleChatWidget::startChat ( const Wt::WString & user)

Start a chat for the given user.

Returns false if the user could not login.

Definition at line 191 of file SimpleChatWidget.C.

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}
bool login(const Wt::WString &user)
Try to login with given user name.
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)
Wt::Core::observing_ptr< Wt::WPushButton > sendButton_
void changeName(const Wt::WString &name)

◆ updateUser()

void SimpleChatWidget::updateUser ( Wt::WCheckBox * b)
private

Definition at line 353 of file SimpleChatWidget.C.

354{
355 users_[b->text()] = b->isChecked();
356}

◆ updateUsers()

void SimpleChatWidget::updateUsers ( )
protectedvirtual

Reimplemented in PopupChatWidget.

Definition at line 320 of file SimpleChatWidget.C.

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}
UserSet users()
Get the users currently logged in.
std::set< Wt::WString > UserSet
Typedef for a collection of user names.
void updateUser(Wt::WCheckBox *b)
std::map< Wt::WString, bool > UserMap

◆ userCount()

int SimpleChatWidget::userCount ( )
inline

Definition at line 56 of file SimpleChatWidget.h.

56{ return users_.size(); }

◆ userName()

const Wt::WString & SimpleChatWidget::userName ( ) const
inline

Definition at line 58 of file SimpleChatWidget.h.

58{ return user_; }

Member Data Documentation

◆ clearInput_

Wt::JSlot SimpleChatWidget::clearInput_
private

Definition at line 80 of file SimpleChatWidget.h.

◆ loggedIn_

bool SimpleChatWidget::loggedIn_
private

Definition at line 78 of file SimpleChatWidget.h.

◆ messageEdit_

Wt::WTextArea* SimpleChatWidget::messageEdit_
private

Definition at line 88 of file SimpleChatWidget.h.

◆ messageReceived_

std::unique_ptr<Wt::WSound> SimpleChatWidget::messageReceived_
private

Definition at line 92 of file SimpleChatWidget.h.

◆ messages_

Wt::WContainerWidget* SimpleChatWidget::messages_
private

Definition at line 87 of file SimpleChatWidget.h.

◆ sendButton_

Wt::Core::observing_ptr<Wt::WPushButton> SimpleChatWidget::sendButton_
private

Definition at line 89 of file SimpleChatWidget.h.

◆ server_

SimpleChatServer& SimpleChatWidget::server_
private

Definition at line 77 of file SimpleChatWidget.h.

◆ statusMsg_

Wt::WText* SimpleChatWidget::statusMsg_
private

Definition at line 85 of file SimpleChatWidget.h.

◆ user_

Wt::WString SimpleChatWidget::user_
private

Definition at line 82 of file SimpleChatWidget.h.

◆ userList_

Wt::Core::observing_ptr<Wt::WContainerWidget> SimpleChatWidget::userList_
private

Definition at line 90 of file SimpleChatWidget.h.

◆ userNameEdit_

Wt::WLineEdit* SimpleChatWidget::userNameEdit_
private

Definition at line 84 of file SimpleChatWidget.h.

◆ users_

UserMap SimpleChatWidget::users_
private

Definition at line 75 of file SimpleChatWidget.h.


The documentation for this class was generated from the following files: