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