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

Generated on Thu Jul 16 2015 for the C++ Web Toolkit (Wt) by doxygen 1.8.9.1