Wt examples 3.3.12
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
26using namespace Wt;
27
30 : WContainerWidget(parent),
31 server_(server),
32 loggedIn_(false),
33 userList_(0),
34 messageReceived_(0)
35{
37 letLogin();
38}
39
45
47{
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);
72
73 WPushButton *b = new WPushButton("Login");
74 hLayout->addWidget(b, 0, AlignMiddle);
75
76 b->clicked().connect(this, &SimpleChatWidget::login);
78
79 vLayout->addWidget(statusMsg_ = new WText());
81}
82
84{
85 if (!loggedIn()) {
86 WString name = userNameEdit_->text();
87
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
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
173{
174 if (flags & RenderFull) {
175 if (loggedIn()) {
176 /* Handle a page refresh correctly */
178 doJavaScript("setTimeout(function() { "
179 + messages_->jsRef() + ".scrollTop += "
180 + messages_->jsRef() + ".scrollHeight;}, 0);");
181 }
182 }
183
184 WContainerWidget::render(flags);
185}
186
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
204 messageEdit_ = new WTextArea();
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 /*
219 * Connect event handlers:
220 * - click on button
221 * - enter in text area
222 *
223 * We will clear the input field using a small custom client-side
224 * JavaScript invocation.
225 */
226
227 // Create a JavaScript 'slot' (JSlot). The JavaScript slot always takes
228 // 2 arguments: the originator of the event (in our case the
229 // button or text area), and the JavaScript event object.
231 ("function(o, e) { setTimeout(function() {"
232 "" + messageEdit_->jsRef() + ".value='';"
233 "}, 0); }");
234
235 /*
236 * Set the connection monitor
237 *
238 * The connection monitor is a javascript monitor that will
239 * nootify the given object by calling the onChange method to
240 * inform of connection change (use of websockets, connection
241 * online/offline) Here we just disable the TextEdit when we are
242 * offline and enable it once we're back online
243 */
244 WApplication::instance()->setConnectionMonitor(
245 "window.monitor={ "
246 "'onChange':function(type, newV) {"
247 "var connected = window.monitor.status.connectionStatus != 0;"
248 "if(connected) {"
249 + messageEdit_->jsRef() + ".disabled=false;"
250 + messageEdit_->jsRef() + ".placeholder='';"
251 "} else { "
252 + messageEdit_->jsRef() + ".disabled=true;"
253 + messageEdit_->jsRef() + ".placeholder='connection lost';"
254 "}"
255 "}"
256 "}"
257 );
258
259 // Bind the C++ and JavaScript event handlers.
260 sendButton_->clicked().connect(this, &SimpleChatWidget::send);
262 sendButton_->clicked().connect(clearInput_);
268
269 // Prevent the enter from generating a new line, which is its default
270 // action
272
273 logoutButton->clicked().connect(this, &SimpleChatWidget::logout);
274
275 WInPlaceEdit *nameEdit = new WInPlaceEdit();
276 nameEdit->addStyleClass("name-edit");
277 nameEdit->setButtonsEnabled(false);
278 nameEdit->setText(user_);
279 nameEdit->valueChanged().connect(this, &SimpleChatWidget::changeName);
280
281 WTemplate *joinMsg = new WTemplate(tr("join-msg.template"), messages_);
282 joinMsg->bindWidget("name", nameEdit);
283 joinMsg->setStyleClass("chat-msg");
284
285 if (!userList_->parent()) {
286 delete userList_;
287 userList_ = 0;
288 }
289
290 if (!sendButton_->parent()) {
291 delete sendButton_;
292 sendButton_ = 0;
293 }
294
295 if (!logoutButton->parent())
296 delete logoutButton;
297
298 updateUsers();
299
300 return true;
301 } else
302 return false;
303}
304
306{
307 if (!name.empty()) {
308 if (server_.changeName(user_, name))
309 user_ = name;
310 }
311}
312
318
320{
321 if (userList_) {
322 userList_->clear();
323
325
326 UserMap oldUsers = users_;
327 users_.clear();
328
329 for (SimpleChatServer::UserSet::iterator i = users.begin();
330 i != users.end(); ++i) {
332 w->setInline(false);
333
334 UserMap::const_iterator j = oldUsers.find(*i);
335 if (j != oldUsers.end())
336 w->setChecked(j->second);
337 else
338 w->setChecked(true);
339
340 users_[*i] = w->isChecked();
342
343 if (*i == user_)
344 w->setStyleClass("chat-self");
345 }
346 }
347}
348
351
353{
354 WCheckBox *b = dynamic_cast<WCheckBox *>(sender());
355 users_[b->text()] = b->isChecked();
356}
357
359{
360 WApplication *app = 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 WText *w = new WText(messages_);
408
409 /*
410 * If it fails, it is because the content wasn't valid XHTML
411 */
412 if (!w->setText(event.formattedHTML(user_, XHTMLText))) {
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 delete 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_)
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()
Wt::WContainerWidget * messages_
Wt::WTextArea * messageEdit_
Wt::WLineEdit * userNameEdit_
void letLogin()
Show a simple login screen.
bool loggedIn() const
Wt::WText * statusMsg_
virtual void render(Wt::WFlags< Wt::RenderFlag > flags)
SimpleChatWidget(SimpleChatServer &server, Wt::WContainerWidget *parent=0)
Create a chat widget that will connect to the given server.
Wt::WPushButton * sendButton_
virtual void createLayout(Wt::WWidget *messages, Wt::WWidget *userList, Wt::WWidget *messageEdit, Wt::WWidget *sendButton, Wt::WWidget *logoutButton)
SimpleChatServer & server_
virtual void newMessage()
void changeName(const Wt::WString &name)
void processChatEvent(const ChatEvent &event)
std::map< Wt::WString, bool > UserMap
Wt::WContainerWidget * userList_
Wt::WSound * messageReceived_
~SimpleChatWidget()
Delete a chat widget.
void preventDefaultAction(bool prevent=true)
Wt::Signals::connection connect(const F &function)
void setJavaScript(const std::string &javaScript, int nbArgs=0)
void setChecked(bool checked)
const WString text() const
void doJavaScript(const std::string &javascript, bool afterLoaded=true)
void enableUpdates(bool enabled=true)
void addLayout(WLayout *layout, int stretch=0, WFlags< AlignmentFlag > alignment=0)
void addWidget(WWidget *widget, int stretch=0, WFlags< AlignmentFlag > alignment=0)
void setResizable(int index, bool enabled=true, const WLength &initialSize=WLength::Auto)
virtual void addStyleClass(const WString &styleClass, bool force=false)
virtual int count() const
void setLayout(WLayout *layout)
virtual void clear()
void setOverflow(Overflow overflow, WFlags< Orientation > orientation=(Horizontal|Vertical))
EventSignal & changed()
Signal< WString > & valueChanged()
void setButtonsEnabled(bool enabled=true)
void setText(const WString &text)
EventSignal & enterPressed()
EventSignal< WMouseEvent > & clicked()
const WString & text() const
static WObject * sender()
void play()
bool empty() const
static const WString Empty
virtual void bindWidget(const std::string &varName, WWidget *widget)
virtual void setText(const WString &text)
const WString & text() const
void setRows(int rows)
bool setText(const WString &text)
bool setTextFormat(TextFormat format)
virtual void doJavaScript(const std::string &javascript)
static WString escapeText(const WString &text, bool newlinesToo=false)
virtual void setFocus(bool focus)
const std::vector< WWidget * > & children() const
virtual void setInline(bool isInline)
virtual void setStyleClass(const WString &styleClass)
void setFocus()
virtual void setStyleClass(const WString &styleClass)=0
std::string jsRef() const
static WString tr(const char *key)
WWidget * parent() const
XHTMLText
PlainText
AlignMiddle
AlignTop
AlignLeft

Generated on Fri May 17 2024 for the C++ Web Toolkit (Wt) by doxygen 1.9.8