1 /* gobby - A GTKmm driven libobby client
2  * Copyright (C) 2005 0x539 dev group
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include <obby/client_buffer.hpp>
20 
21 #include <gtkmm/stock.h>
22 #include <gtkmm/window.h>
23 
24 #include <obby/format_string.hpp>
25 #include "common.hpp"
26 #include "chat.hpp"
27 
28 namespace
29 {
30 	// Checks if name is highlightend in text.
is_highlighted(const Glib::ustring & text,const Glib::ustring & name)31 	bool is_highlighted(const Glib::ustring& text,
32 	                    const Glib::ustring& name)
33 	{
34 		Glib::ustring::size_type pos = 0;
35 		while( (pos = text.find(name, pos)) != Glib::ustring::npos)
36 		{
37 			// Check that the found position is not part of another
38 			// word ('ck' should not be found in 'luck' and such).
39 			if(pos > 0 && Glib::Unicode::isalnum(text[pos - 1]) )
40 				{ ++ pos; continue; }
41 
42 			if(pos + name.length() < text.length() &&
43 			   Glib::Unicode::isalnum(text[pos + name.length()]))
44 				{ ++ pos; continue; }
45 
46 			// Found occurence
47 			return true;
48 		}
49 
50 		return false;
51 	}
52 
each_line(const std::string & text,const sigc::slot<void,const std::string &> func)53 	void each_line(const std::string& text,
54 	               const sigc::slot<void, const std::string&> func)
55 	{
56 		std::string::size_type prev = 0, pos = 0;
57 		while( (pos = text.find('\n', pos)) != std::string::npos)
58 		{
59 			func(text.substr(prev, pos - prev) );
60 			prev = ++pos;
61 		}
62 
63 		func(text.substr(prev) );
64 	}
65 
66 }
67 
Chat(Gtk::Window & parent,const Preferences & preferences)68 Gobby::Chat::Chat(Gtk::Window& parent, const Preferences& preferences)
69  : Gtk::VBox(), m_parent(parent), m_preferences(preferences),
70    m_buffer(NULL), m_img_btn(Gtk::Stock::JUMP_TO, Gtk::ICON_SIZE_BUTTON),
71    m_focus(false)
72 {
73 	m_btn_chat.set_label(_("Send"));
74 	m_btn_chat.set_image(m_img_btn);
75 
76 	m_btn_chat.signal_clicked().connect(
77 		sigc::mem_fun(*this, &Chat::on_chat) );
78 	m_ent_chat.signal_activate().connect(
79 		sigc::mem_fun(*this, &Chat::on_chat) );
80 
81 	m_wnd_chat.add(m_log_chat);
82 	m_wnd_chat.set_shadow_type(Gtk::SHADOW_IN);
83 	m_wnd_chat.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
84 
85 	m_box_chat.pack_start(m_ent_chat, Gtk::PACK_EXPAND_WIDGET);
86 	m_box_chat.pack_start(m_btn_chat, Gtk::PACK_SHRINK);
87 
88 	m_box_chat.set_spacing(5);
89 
90 	pack_start(m_wnd_chat, Gtk::PACK_EXPAND_WIDGET);
91 	pack_start(m_box_chat, Gtk::PACK_SHRINK);
92 
93 	set_spacing(5);
94 	set_sensitive(false);
95 	//m_ent_chat.set_sensitive(false);
96 	//m_btn_chat.set_sensitive(false);
97 
98 #ifdef GTKMM_GEQ_28
99 	m_parent.signal_focus_in_event().connect(
100 		sigc::mem_fun(*this, &Chat::on_focus_in)
101 	);
102 
103 	m_parent.signal_focus_out_event().connect(
104 		sigc::mem_fun(*this, &Chat::on_focus_out)
105 	);
106 #endif
107 
108 	set_focus_child(m_log_chat);
109 }
110 
~Chat()111 Gobby::Chat::~Chat()
112 {
113 }
114 
115 #ifdef GTKMM_GEQ_28
116 // m_parent.has_focus() seems not to work, so we keep track of focus
117 // ourselves
on_focus_in(GdkEventFocus * event)118 bool Gobby::Chat::on_focus_in(GdkEventFocus* event)
119 {
120 	m_focus = true;
121 	m_parent.set_urgency_hint(false);
122 	return false;
123 }
124 
on_focus_out(GdkEventFocus * event)125 bool Gobby::Chat::on_focus_out(GdkEventFocus* event)
126 {
127 	m_focus = false;
128 	return false;
129 }
130 #endif
131 
obby_start(LocalBuffer & buf)132 void Gobby::Chat::obby_start(LocalBuffer& buf)
133 {
134 	m_buffer = &buf;
135 
136 	m_log_chat.clear();
137 	m_ent_chat.set_sensitive(true);
138 	m_btn_chat.set_sensitive(true);
139 
140 	set_sensitive(true);
141 
142 	buf.get_command_queue().query_failed_event().connect(
143 		sigc::mem_fun(*this, &Chat::on_query_failed)
144 	);
145 
146 	buf.get_command_queue().help_event().connect(
147 		sigc::mem_fun(*this, &Chat::on_help)
148 	);
149 
150 	buf.get_command_queue().result_event("remove").connect(
151 		sigc::mem_fun(*this, &Chat::on_remove_result)
152 	);
153 
154 	const obby::chat& chat = buf.get_chat();
155 	chat.message_event().connect(
156 		sigc::mem_fun(*this, &Chat::on_message) );
157 
158 	for(obby::chat::message_iterator iter = chat.message_begin();
159 	    iter != chat.message_end();
160 	    ++ iter)
161 		on_message(*iter);
162 }
163 
obby_end()164 void Gobby::Chat::obby_end()
165 {
166 	m_buffer = NULL;
167 
168 	m_ent_chat.clear_history();
169 	m_ent_chat.set_sensitive(false);
170 	m_btn_chat.set_sensitive(false);
171 }
172 
obby_user_join(const obby::user & user)173 void Gobby::Chat::obby_user_join(const obby::user& user)
174 {
175 }
176 
obby_user_part(const obby::user & user)177 void Gobby::Chat::obby_user_part(const obby::user& user)
178 {
179 }
180 
obby_document_insert(LocalDocumentInfo & document)181 void Gobby::Chat::obby_document_insert(LocalDocumentInfo& document)
182 {
183 }
184 
obby_document_remove(LocalDocumentInfo & document)185 void Gobby::Chat::obby_document_remove(LocalDocumentInfo& document)
186 {
187 }
188 
on_chat()189 void Gobby::Chat::on_chat()
190 {
191 	if(m_buffer == NULL)
192 		throw std::logic_error("Gobby::Chat::on_chat");
193 
194 	Glib::ustring message = m_ent_chat.get_text();
195 	if(message.empty() ) return;
196 	/* set_text("") did crash on Vista */
197 	m_ent_chat.delete_text(0, -1);
198 
199 	// Commands beginning with /
200 	if(message[0] == '/')
201 	{
202 		Glib::ustring::size_type pos = message.find_first_of(" \n\v\t");
203 		if(pos != Glib::ustring::npos)
204 		{
205 			obby::command_query query(
206 				message.substr(1, pos - 1),
207 				message.substr(pos + 1)
208 			);
209 
210 			m_buffer->send_command(query);
211 		}
212 		else
213 		{
214 			obby::command_query query(message.substr(1), "");
215 			m_buffer->send_command(query);
216 		}
217 	}
218 	else
219 	{
220 		// Send each line separately
221 		each_line(
222 			message,
223 			sigc::mem_fun(*this, &Gobby::Chat::send_line)
224 		);
225 	}
226 }
227 
on_query_failed(const obby::command_query & query)228 void Gobby::Chat::on_query_failed(const obby::command_query& query)
229 {
230 	obby::format_string str(
231 		_("Command '%0%' not found. Type /help for a list of "
232 		  "existing commands.")
233 	);
234 
235 	str << query.get_command();
236 	m_log_chat.log(str.str(), "red", std::time(NULL) );
237 }
238 
on_help(const std::string & name,const std::string & desc)239 void Gobby::Chat::on_help(const std::string& name, const std::string& desc)
240 {
241 	obby::format_string str(
242 		_("%0%: %1%")
243 	);
244 
245 	str << name << desc;
246 	m_log_chat.log(str.str(), "", std::time(NULL) );
247 }
248 
on_remove_result(const obby::command_query & query,const obby::command_result & result)249 void Gobby::Chat::on_remove_result(const obby::command_query& query,
250                                    const obby::command_result& result)
251 {
252 	if(result.get_type() != obby::command_result::REPLY) return;
253 
254 	if(result.get_reply() == "doc_not_found")
255 	{
256 		obby::format_string str(_("Document %0% does not exist") );
257 		str << query.get_paramlist();
258 		m_log_chat.log(str.str(), "red", std::time(NULL) );
259 	}
260 	else if(result.get_reply() == "no_doc_given")
261 	{
262 		Glib::ustring str(_("Usage: /remove <document>") );
263 		m_log_chat.log(str, "red", std::time(NULL) );
264 	}
265 }
266 
on_message(const obby::chat::message & message)267 void Gobby::Chat::on_message(const obby::chat::message& message)
268 {
269 	const obby::chat::user_message* user_message =
270 		dynamic_cast<const obby::chat::user_message*>(&message);
271 	const obby::chat::server_message* server_message =
272 		dynamic_cast<const obby::chat::server_message*>(&message);
273 	const obby::chat::system_message* system_message =
274 		dynamic_cast<const obby::chat::system_message*>(&message);
275 
276 	if(user_message != NULL)
277 		on_user_message(*user_message);
278 	else if(server_message != NULL)
279 		on_server_message(*server_message);
280 	else if(system_message != NULL)
281 		on_system_message(*system_message);
282 	else
283 		throw std::logic_error("Gobby::Chat::on_message");
284 
285 #ifdef GTKMM_GEQ_28
286 	if(!m_focus && m_preferences.appearance.urgency_hint && is_visible())
287 		m_parent.set_urgency_hint(true);
288 #endif
289 }
290 
on_user_message(const obby::chat::user_message & message)291 void Gobby::Chat::on_user_message(const obby::chat::user_message& message)
292 {
293 	// Split received message up into lines
294 	each_line(
295 		message.repr(),
296 		sigc::bind(
297 			sigc::mem_fun(*this, &Gobby::Chat::recv_user_line),
298 			sigc::ref(message)
299 		)
300 	);
301 }
302 
on_server_message(const obby::chat::server_message & message)303 void Gobby::Chat::on_server_message(const obby::chat::server_message& message)
304 {
305 	// Split received message up into lines
306 	each_line(
307 		message.repr(),
308 		sigc::bind(
309 			sigc::mem_fun(*this, &Gobby::Chat::recv_server_line),
310 			sigc::ref(message)
311 		)
312 	);
313 }
314 
on_system_message(const obby::chat::system_message & message)315 void Gobby::Chat::on_system_message(const obby::chat::system_message& message)
316 {
317 	each_line(
318 		message.repr(),
319 		sigc::bind(
320 			sigc::mem_fun(*this, &Gobby::Chat::recv_system_line),
321 			sigc::ref(message)
322 		)
323 	);
324 }
325 
send_line(const std::string & line)326 void Gobby::Chat::send_line(const std::string& line)
327 {
328 	m_buffer->send_message(line);
329 }
330 
recv_user_line(const std::string & line,const obby::chat::user_message & message)331 void Gobby::Chat::recv_user_line(const std::string& line,
332                                  const obby::chat::user_message& message)
333 {
334 	// Check each line for highlighting occurence
335 	Glib::ustring colour = "";
336 	if(&message.get_user() != &m_buffer->get_self())
337 		if(is_highlighted(line, m_buffer->get_self().get_name()) )
338 			colour = "darkred";
339 
340 	m_log_chat.log(line, colour, message.get_timestamp() );
341 }
342 
recv_server_line(const std::string & line,const obby::chat::server_message & message)343 void Gobby::Chat::recv_server_line(const std::string& line,
344                                    const obby::chat::server_message& message)
345 {
346 	m_log_chat.log(line, "forest green", message.get_timestamp());
347 }
348 
recv_system_line(const std::string & line,const obby::chat::system_message & message)349 void Gobby::Chat::recv_system_line(const std::string& line,
350                                    const obby::chat::system_message& message)
351 {
352 	m_log_chat.log(line, "blue", message.get_timestamp() );
353 }
354