Update list of wide characters
[centerim5.git] / src / Conversations.cpp
blob16891b7b98e3295a5034fc4be44518d8df91a0c1
1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
3 //
4 // This file is part of CenterIM.
5 //
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
19 #include "Conversations.h"
21 #include "gettext.h"
22 #include <cstring>
24 Conversations *Conversations::my_instance_ = nullptr;
26 Conversations *Conversations::instance()
28 return my_instance_;
31 void Conversations::onScreenResized()
33 CppConsUI::Rect r = CENTERIM->getScreenArea(CenterIM::CHAT_AREA);
34 r.y = r.getBottom();
35 r.height = 1;
37 moveResizeRect(r);
40 void Conversations::focusActiveConversation()
42 activateConversation(active_);
45 void Conversations::focusConversation(int i)
47 g_assert(i >= 1);
49 if (conversations_.empty())
50 return;
52 // The external conversation #1 is the internal conversation #0.
53 i -= 1;
55 int s = static_cast<int>(conversations_.size());
56 if (i < s)
57 activateConversation(i);
58 else {
59 // If there is less than i active conversations then active the last one.
60 activateConversation(s - 1);
64 void Conversations::focusPrevConversation()
66 int i = prevActiveConversation(active_);
67 if (i != -1)
68 activateConversation(i);
71 void Conversations::focusNextConversation()
73 int i = nextActiveConversation(active_);
74 if (i != -1)
75 activateConversation(i);
78 void Conversations::setExpandedConversations(bool expanded)
80 if (expanded) {
81 // Make small room on the left and right.
82 left_spacer_->setWidth(1);
83 right_spacer_->setWidth(1);
85 else {
86 left_spacer_->setWidth(0);
87 right_spacer_->setWidth(0);
90 // Trigger the Conversation::show() method to change the scrollbar setting of
91 // the active conversation.
92 activateConversation(active_);
95 Conversations::Conversations()
96 : Window(0, 0, 80, 1, TYPE_NON_FOCUSABLE, false), active_(-1)
98 setColorScheme(CenterIM::SCHEME_CONVERSATION);
100 outer_list_ = new CppConsUI::HorizontalListBox(AUTOSIZE, 1);
101 addWidget(*outer_list_, 0, 0);
103 left_spacer_ = new CppConsUI::Spacer(0, 1);
104 right_spacer_ = new CppConsUI::Spacer(0, 1);
105 conv_list_ = new CppConsUI::HorizontalListBox(AUTOSIZE, 1);
107 outer_list_->appendWidget(*left_spacer_);
108 outer_list_->appendWidget(*conv_list_);
109 outer_list_->appendWidget(*right_spacer_);
111 // Init preferences.
112 purple_prefs_add_none(CONF_PREFIX "/chat");
113 purple_prefs_add_int(CONF_PREFIX "/chat/partitioning", 80);
114 purple_prefs_add_int(CONF_PREFIX "/chat/roomlist_partitioning", 80);
115 purple_prefs_add_bool(CONF_PREFIX "/chat/beep_on_msg", false);
117 // send_typing caching.
118 send_typing_ = purple_prefs_get_bool("/purple/conversations/im/send_typing");
119 purple_prefs_connect_callback(this, "/purple/conversations/im/send_typing",
120 send_typing_pref_change_, this);
122 std::memset(&centerim_conv_ui_ops_, 0, sizeof(centerim_conv_ui_ops_));
123 centerim_conv_ui_ops_.create_conversation = create_conversation_;
124 centerim_conv_ui_ops_.destroy_conversation = destroy_conversation_;
125 // centerim_conv_ui_ops_.write_chat = ;
126 // centerim_conv_ui_ops_.write_im = ;
127 centerim_conv_ui_ops_.write_conv = write_conv_;
128 centerim_conv_ui_ops_.chat_add_users = chat_add_users_;
129 centerim_conv_ui_ops_.chat_rename_user = chat_rename_user_;
130 centerim_conv_ui_ops_.chat_remove_users = chat_remove_users_;
131 centerim_conv_ui_ops_.chat_update_user = chat_update_user_;
133 centerim_conv_ui_ops_.present = present_;
134 // centerim_conv_ui_ops_.has_focus = ;
135 // centerim_conv_ui_ops_.custom_smiley_add = ;
136 // centerim_conv_ui_ops_.custom_smiley_write = ;
137 // centerim_conv_ui_ops_.custom_smiley_close = ;
138 // centerim_conv_ui_ops_.send_confirm = ;
140 // Setup callbacks for conversations.
141 purple_conversations_set_ui_ops(&centerim_conv_ui_ops_);
143 void *handle = purple_conversations_get_handle();
144 purple_signal_connect(
145 handle, "buddy-typing", this, PURPLE_CALLBACK(buddy_typing_), this);
146 purple_signal_connect(
147 handle, "buddy-typing-stopped", this, PURPLE_CALLBACK(buddy_typing_), this);
149 // Setup callbacks for connections in relation to conversations.
150 void *connections_handle = purple_connections_get_handle();
151 purple_signal_connect(connections_handle, "signed-on", this,
152 PURPLE_CALLBACK(account_signed_on_), this);
154 onScreenResized();
157 Conversations::~Conversations()
159 // Close all opened conversations.
160 while (!conversations_.empty())
161 purple_conversation_destroy(
162 conversations_.front().conv->getPurpleConversation());
164 purple_conversations_set_ui_ops(nullptr);
165 purple_prefs_disconnect_by_handle(this);
166 purple_signals_disconnect_by_handle(this);
169 void Conversations::init()
171 g_assert(my_instance_ == nullptr);
173 my_instance_ = new Conversations;
174 my_instance_->show();
177 void Conversations::finalize()
179 g_assert(my_instance_ != nullptr);
181 delete my_instance_;
182 my_instance_ = nullptr;
185 int Conversations::findConversation(PurpleConversation *conv)
187 for (int i = 0; i < static_cast<int>(conversations_.size()); ++i)
188 if (conversations_[i].conv->getPurpleConversation() == conv)
189 return i;
191 return -1;
194 int Conversations::prevActiveConversation(int current)
196 g_assert(current < static_cast<int>(conversations_.size()));
198 if (conversations_.empty()) {
199 // No conversations.
200 return -1;
203 if (current == 0) {
204 // Return the last conversation.
205 return conversations_.size() - 1;
208 return current - 1;
211 int Conversations::nextActiveConversation(int current)
213 g_assert(current < static_cast<int>(conversations_.size()));
215 if (conversations_.empty()) {
216 // No conversations.
217 return -1;
220 if (current == static_cast<int>(conversations_.size() - 1)) {
221 // Return the first conversation.
222 return 0;
225 return current + 1;
228 void Conversations::activateConversation(int i)
230 g_assert(i >= -1);
231 g_assert(i < static_cast<int>(conversations_.size()));
233 if (active_ == i) {
234 if (active_ != -1)
235 conversations_[active_].conv->show();
236 return;
239 if (i != -1) {
240 // Show a new active conversation.
241 conversations_[i].label->setVisibility(true);
242 conversations_[i].label->setColorScheme(
243 CenterIM::SCHEME_CONVERSATION_ACTIVE);
244 conversations_[i].conv->show();
247 // Hide old active conversation if there is any.
248 if (active_ != -1) {
249 conversations_[active_].label->setColorScheme(0);
250 conversations_[active_].conv->hide();
253 active_ = i;
256 void Conversations::updateLabel(int i)
258 g_assert(i >= 0);
259 g_assert(i < static_cast<int>(conversations_.size()));
261 char *name = g_strdup_printf(
262 " %d|%s%c", i + 1, purple_conversation_get_title(
263 conversations_[i].conv->getPurpleConversation()),
264 conversations_[i].typing_status);
265 conversations_[i].label->setText(name);
266 g_free(name);
269 void Conversations::updateLabels()
271 // Note: This can be a little slow if there are too many open conversations.
272 for (int i = 0; i < static_cast<int>(conversations_.size()); ++i)
273 updateLabel(i);
276 void Conversations::create_conversation(PurpleConversation *conv)
278 g_return_if_fail(conv != nullptr);
279 g_return_if_fail(findConversation(conv) == -1);
281 PurpleConversationType type = purple_conversation_get_type(conv);
282 if (type != PURPLE_CONV_TYPE_IM && type != PURPLE_CONV_TYPE_CHAT) {
283 purple_conversation_destroy(conv);
284 LOG->error(_("Unhandled conversation type '%d'."), type);
285 return;
288 auto conversation = new Conversation(conv);
290 conv->ui_data = static_cast<void *>(conversation);
292 ConvChild c;
293 c.conv = conversation;
294 c.label = new CppConsUI::Label(AUTOSIZE, 1);
295 c.typing_status = ' ';
296 conv_list_->appendWidget(*c.label);
297 conversations_.push_back(c);
298 updateLabels();
300 // Show the first conversation if there is not any already.
301 if (active_ == -1)
302 activateConversation(conversations_.size() - 1);
305 void Conversations::destroy_conversation(PurpleConversation *conv)
307 g_return_if_fail(conv != nullptr);
309 int i = findConversation(conv);
311 // Destroying unhandled conversation type.
312 if (i == -1)
313 return;
315 if (i == active_) {
316 if (conversations_.size() == 1) {
317 // The last conversation is closed.
318 active_ = -1;
320 else {
321 if (active_ == static_cast<int>(conversations_.size() - 1))
322 focusPrevConversation();
323 else
324 focusNextConversation();
328 delete conversations_[i].conv;
329 conv_list_->removeWidget(*conversations_[i].label);
330 conversations_.erase(conversations_.begin() + i);
332 if (active_ > i) {
333 // Fix up the number of the active conversation.
334 --active_;
337 updateLabels();
340 void Conversations::write_conv(PurpleConversation *conv, const char *name,
341 const char *alias, const char *message, PurpleMessageFlags flags,
342 time_t mtime)
344 g_return_if_fail(conv != nullptr);
346 int i = findConversation(conv);
348 // Message to unhandled conversation type.
349 if (i == -1)
350 return;
352 if (i != active_)
353 conversations_[i].label->setColorScheme(CenterIM::SCHEME_CONVERSATION_NEW);
355 // Delegate it to Conversation object.
356 conversations_[i].conv->write(name, alias, message, flags, mtime);
359 ConversationRoomList *Conversations::getRoomList(PurpleConversation *conv)
361 if (conv != nullptr) {
362 Conversation *conversation = static_cast<Conversation *>(conv->ui_data);
363 if (conversation != nullptr)
364 return conversation->getRoomList();
367 return nullptr;
370 void Conversations::chat_add_users(
371 PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
373 ConversationRoomList *room_list = getRoomList(conv);
374 if (room_list != nullptr)
375 room_list->add_users(cbuddies, new_arrivals);
378 void Conversations::chat_rename_user(PurpleConversation *conv,
379 const char *old_name, const char *new_name, const char *new_alias)
381 ConversationRoomList *room_list = getRoomList(conv);
382 if (room_list != nullptr)
383 room_list->rename_user(old_name, new_name, new_alias);
386 void Conversations::chat_remove_users(PurpleConversation *conv, GList *users)
388 ConversationRoomList *room_list = getRoomList(conv);
389 if (room_list != nullptr)
390 room_list->remove_users(users);
393 void Conversations::chat_update_user(PurpleConversation *conv, const char *user)
395 ConversationRoomList *room_list = getRoomList(conv);
396 if (room_list != nullptr)
397 room_list->update_user(user);
400 void Conversations::present(PurpleConversation *conv)
402 g_return_if_fail(conv);
404 int i = findConversation(conv);
406 // Unhandled conversation type.
407 if (i == -1)
408 return;
410 activateConversation(i);
413 void Conversations::buddy_typing(PurpleAccount *account, const char *who)
415 PurpleConversation *conv =
416 purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account);
417 if (!conv)
418 return;
420 int i = findConversation(conv);
421 if (i == -1) {
422 // This should probably never happen.
423 return;
426 PurpleConvIm *im = PURPLE_CONV_IM(conv);
427 g_assert(im != nullptr);
428 if (purple_conv_im_get_typing_state(im) == PURPLE_TYPING)
429 conversations_[i].typing_status = '*';
430 else
431 conversations_[i].typing_status = ' ';
433 updateLabel(i);
436 void Conversations::account_signed_on(PurpleConnection *gc)
438 for (ConvChild &conv_child : conversations_) {
439 PurpleConversation *purple_conv = conv_child.conv->getPurpleConversation();
441 // Only process chats for this connection.
442 if (purple_conversation_get_type(purple_conv) != PURPLE_CONV_TYPE_CHAT ||
443 purple_conversation_get_gc(purple_conv) != gc)
444 continue;
446 // TODO Add and consult the "want-to-rejoin" configuration parameter?
448 // Look up the chat from the buddy list.
449 PurpleChat *purple_chat =
450 purple_blist_find_chat(purple_conversation_get_account(purple_conv),
451 purple_conversation_get_name(purple_conv));
453 GHashTable *components = NULL;
455 if (purple_chat != nullptr)
456 components = purple_chat_get_components(purple_chat);
457 else {
458 // Use defaults if the chat cannot be found.
459 PurplePluginProtocolInfo *info =
460 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
461 components =
462 info->chat_info_defaults(gc, purple_conversation_get_name(purple_conv));
465 serv_join_chat(gc, components);
469 void Conversations::send_typing_pref_change(
470 const char *name, PurplePrefType /*type*/, gconstpointer /*val*/)
472 g_assert(std::strcmp(name, "/purple/conversations/im/send_typing") == 0);
473 send_typing_ = purple_prefs_get_bool(name);
476 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: