Wt examples  4.0.3
Public Member Functions | Protected Member Functions | Private Types | Private Member Functions | Private Attributes | List of all members
SimpleChatWidget Class Reference

A self-contained chat widget. More...

#include <SimpleChatWidget.h>

Inheritance diagram for SimpleChatWidget:
Inheritance graph
[legend]

Public Member Functions

 SimpleChatWidget (SimpleChatServer &server)
 Create a chat widget that will connect to the given server. More...
 
 ~SimpleChatWidget ()
 Delete a chat widget. More...
 
void connect ()
 
void disconnect ()
 
void letLogin ()
 Show a simple login screen. More...
 
bool startChat (const Wt::WString &user)
 Start a chat for the given user. More...
 
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

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

◆ ~SimpleChatWidget()

SimpleChatWidget::~SimpleChatWidget ( )

Delete a chat widget.

Definition at line 37 of file SimpleChatWidget.C.

38 {
39  messageReceived_.reset();
40  logout();
41 }
std::unique_ptr< Wt::WSound > messageReceived_

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 }
SimpleChatServer & server_
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 {
45  if (server_.connect
46  (this, std::bind(&SimpleChatWidget::processChatEvent, this, std::placeholders::_1)))
47  Wt::WApplication::instance()->enableUpdates(true);
48 }
SimpleChatServer & server_
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 = Wt::cpp14::make_unique<Wt::WVBoxLayout>();
132 
133  // Create a horizontal layout for the messages | userslist.
134  auto hLayout = Wt::cpp14::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 = Wt::cpp14::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 }
SimpleChatServer & server_
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(Wt::cpp14::make_unique<Wt::WVBoxLayout>());
61 
62  auto hLayout_(Wt::cpp14::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(Wt::cpp14::make_unique<Wt::WLabel>("User name:"),
68  0, Wt::AlignmentFlag::Middle);
69 
70  userNameEdit_ = hLayout->addWidget(Wt::cpp14::make_unique<Wt::WLineEdit>(user_),
71  0, Wt::AlignmentFlag::Middle);
72  userNameEdit_->setFocus();
73 
74  auto button = hLayout->addWidget(Wt::cpp14::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(Wt::cpp14::make_unique<Wt::WText>());
81  statusMsg_->setTextFormat(Wt::TextFormat::Plain);
82 }
Wt::WText * statusMsg_
Wt::WLineEdit * userNameEdit_

◆ 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 
89  if (!messageReceived_)
90  messageReceived_ = Wt::cpp14::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 }
Wt::WText * statusMsg_
std::unique_ptr< Wt::WSound > messageReceived_
bool startChat(const Wt::WString &user)
Start a chat for the given user.
Wt::WLineEdit * userNameEdit_
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 }
SimpleChatServer & server_
void logout(const Wt::WString &user)
Logout from the server.
void letLogin()
Show a simple login screen.
bool loggedIn() const

◆ 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(Wt::cpp14::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 }
Type type() const
Get the event type.
Wt::WContainerWidget * messages_
std::unique_ptr< Wt::WSound > messageReceived_
const Wt::WString & data() const
Get the extra data for this event.
virtual void updateUsers()
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_
const Wt::WString formattedHTML(const Wt::WString &user, Wt::TextFormat format) const
Get the message formatted as HTML, rendered for the given user.
const Wt::WString & user() const
Get the user who caused the event.
bool loggedIn() const
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::WContainerWidget * messages_
Wt::WTextArea * messageEdit_
bool loggedIn() const

◆ send()

void SimpleChatWidget::send ( )
private

Definition at line 314 of file SimpleChatWidget.C.

315 {
316  if (!messageEdit_->text().empty())
318 }
SimpleChatServer & server_
Wt::WTextArea * messageEdit_
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_; }
SimpleChatServer & 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 = Wt::cpp14::make_unique<WContainerWidget>();
207  auto userListPtr = Wt::cpp14::make_unique<WContainerWidget>();
208  auto messageEditPtr = Wt::cpp14::make_unique<Wt::WTextArea>();
209  auto sendButtonPtr = Wt::cpp14::make_unique<Wt::WPushButton>("Send");
210  auto logoutButtonPtr = Wt::cpp14::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 = Wt::cpp14::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(Wt::cpp14::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 }
Wt::WContainerWidget * messages_
SimpleChatServer & server_
Wt::WTextArea * messageEdit_
void changeName(const Wt::WString &name)
virtual void updateUsers()
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_
Wt::Core::observing_ptr< Wt::WPushButton > sendButton_
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::WLineEdit * userNameEdit_
bool login(const Wt::WString &user)
Try to login with given user 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(Wt::cpp14::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 }
SimpleChatServer & server_
std::map< Wt::WString, bool > UserMap
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)
Wt::Core::observing_ptr< Wt::WContainerWidget > userList_

◆ 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:

Generated on Mon Jan 14 2019 for the C++ Web Toolkit (Wt) by doxygen 1.8.14